define([], function() {

  function localPro(ctl, key, defaultValue, i18n) {
    return getProp(ctl, 'local', key, defaultValue, i18n)
  }

  function getProp(ctl, scope, key, defaultValue, i18n) {
    if (ctl.profile.props && ctl.profile.props[scope]) {
      if (ctl.profile.props[scope][key] !== undefined) {
        let str = ctl.profile.props[scope][key];
        if (i18n)
          return i18nResource(ctl, str);
        return str;
      }
    }
    return defaultValue;
  }

  function i18nResource(ctl, value) {
    if (!value)
      return '';
    if (!isNaN(value))
      return value;

    let resource = ctl.runtime.struct.i18n[$E.lang];
    //console.log(value + ' => ' + $E.lang, resource)
    var matches = value.match(/\{\{([\u4e00-\u9fa5 | \w | \. | @ | %]+)\}\}/gi), ex, v;
    if (matches) {
      for (var i = 0; i < matches.length; i++) {
        value = value.replace(matches[i], function () {
          ex = (/\{\{([\u4e00-\u9fa5 | \w | \. | @ | %]+)\}\}/gi).exec(matches[i]);
          if (resource && resource.hasOwnProperty(ex[1]))
            return resource[ex[1]];
          else
            return ex[1];
          //return matches[i];
        })
      }
    }
    return value;
  }

  function doStructCmd(ctl, cmdId, values) {
    return ctl.instance.doCmd(cmdId, {
      CTRL: ctl,
      RUNTIME: ctl.runtime,
      EVENT: values
    }, ctl.runtime, ctl.id);
  }

  function doCmd(ctl, cmdId, values) {
    let R, ctlCmds = ctl.profile.cmds || {};
    if (ctlCmds[cmdId]) {
      R = ctl.instance.dc.doCmdList(cmdId + '@' + ctl.path, ctlCmds[cmdId].items, '', {
        CTRL: ctl,
        RUNTIME: ctl.runtime,
        EVENT: values
      }, ctl.runtime);
    }
    return R;
  }

  function isInlineCtl(ctl) {
    return ['INPUT', 'LABEL', 'COMBOX', 'NUMBOX', 'IMAGE', 'DROPMENU'].contains(ctl.type);
  }

  /**
   * 对控件进行风格收集
   */
  function makeStyles(ctl) {
    let styles = (ctl.profile.props ? ctl.profile.props.styles : {}),
      skin = {
        clazzs: ['dyn_ctl'],
        styles: {},
        inputStyles: {},
        labelStyles: {}
      };
    if (styles) {
      for (let key in styles) {
        if (key == 'customStyles') {
          let cs = styles.customStyles;
          if (cs.classes) {
            skin.clazzs = skin.clazzs.concat(cs.classes.split(' '));
          }
          if (cs.styles) {
            if (cs.styles.startsWith('!style')) {
              let styleFragment = cs.styles.substr(6),
                resetClass = 'reset_' + ctl.path.replace(/\./g, '_'),
                meta = $('head #' + resetClass);
              styleFragment = styleFragment.replace(/@me/gi, '.' + resetClass);
              skin.clazzs.push(resetClass);
              if (!meta.length) {
                $('<style type="text/css" id="' + resetClass + '" />').appendTo($('head')).text(styleFragment);
              }
            } else {
              $.each(cs.styles.split(';'), function (i, v) {
                v = v.replace(/(?:^[\t\n\r]+)|(?:[\t\n\r]+$)/g, '').trim();
                if (v[0] == '@')
                  addCSS(skin.inputStyles, v.substring(1));
                else if (v[0] == '#')
                  addCSS(skin.labelStyles, v.substring(1));
                else
                  addCSS(skin.styles, v);
              })
            }
          }
        } else if (key == 'font') {
          let data = styles[key];
          skin.styles['fontFamily'] = data.f;
          skin.styles['fontSize'] = data.s;
          skin.styles['fontWeight'] = data.b;
          skin.styles['fontStyle'] = data.i;
        } else if (key == 'backgroundImage') {
          let img, data = styles[key];
          if (data.url) {
            if (data.fs)
              img = 'url(\'' + $E.url + '/fs/get?file=' + data.url + '\')'; else {
              if ((data.url || '').toLowerCase().startWith('http'))
                img = 'url(\'' + data.url + '\')';
              else if ((data.url || '').toLowerCase().startWith('data:'))
                img = 'url(\'' + data.url + '\')';
              else
                img = 'url(\'' + $E.url + data.url + '\')'
            }
            skin.styles['backgroundImage'] = img;
            if (data.x == undefined)
              skin.styles['backgroundPositionX'] = 'center';
            else
              skin.styles['backgroundPositionX'] = data.x;
            if (data.y == undefined)
              skin.styles['backgroundPositionY'] = 'center';
            else
              skin.styles['backgroundPositionY'] = data.y;
            if (data.r == undefined)
              skin.styles['backgroundRepeat'] = 'no-repeat';
            else
              skin.styles['backgroundRepeat'] = data.r;
          }
        } else if (key == 'flex') {
          skin.styles['flex'] = styles[key] + ' 1 0';
        } else
          skin.styles[key] = styles[key];
      }
    }
    if (!skin.styles['position'])
      skin.styles['position'] = 'relative';

    if (!skin.styles['display'] && isInlineCtl(ctl))
      skin.styles['display'] = 'inline-block';
    return skin;
  }

  function addCSS(styles, cssText) {
    let i = cssText.indexOf(':');
    if (i > 0) {
      styles[cssText.substring(0, i).trim()] = cssText.substring(i + 1).trim();
    }
  }

  /**
   * 执行控件绘制前事务
   */
  function beforeRender(ctl) {
    let ctlCmds = ctl.profile.cmds || {}
    if (ctlCmds['before']) {
      // 执行控件的渲染时命令
      $F.logGroup('Ctls2', '控件绘制前命令:' + ctl.path)
      doCmd(ctl, 'before', {});
      $F.logGroupEnd();
    }
  }

  /**
   * 执行控件绘制后事务
   */
  function afterCtlRander(ctl) {
    let ctlCmds = ctl.profile.cmds || {}
    if (ctlCmds['after']) {
      // 执行控件的渲染时命令
      $F.logGroup('Ctls2', '控件绘制后命令:' + ctl.path)
      doCmd(ctl, 'after', {});
      $F.logGroupEnd();
    }

    ctl.dom.on('click', function () {
      // TODO: bubble event to xitem
    })
  }

  /**
   * 绑定变量事件以及执行绘制后事件
   */
  function makeValueAndState(ctl) {
    let instance = ctl.instance,
      runtime = ctl.runtime,
      xitemEv = {},
      tooltip = getProp(ctl, 'local', 'tooltip'),
      ref = getProp(ctl, 'local', 'tipref'),
      evt = getProp(ctl, 'local', 'tipevt'),
      dsSerial = getProp(ctl, 'general', 'dsSerial');
    if ((tooltip || ref) && ctl.dom) {
      ctl.dom.attr('title', i18nResource(ctl, tooltip));
      if (getProp(ctl, 'local', 'tipper') == 'Tipped') {
        // 使用Tipped作为tip显示器
        let options = {}, pos = getProp(ctl, 'local', 'tippos');
        if (pos)
          options.position = pos;
        if (ref) {
          var isHideOn = ctl.attributes.hideOn ? true : false;
          $.extend(options, {
            hideOn: isHideOn ? false : {element: 'mouseleave', tooltip: 'mouseleave'},
            hideOnClickOutside: isHideOn,
            ajax: {
              url: 'help/tipped',
              simulator: function (tipCallback, element) {
                var refctl = runtime.ctls[ref];
                refctl.load();
                refctl.closeTipped = function () {
                  Tipped.hideAll(this);
                }
                if (evt) {
                  var ev = $.extend(xitemEv, {
                    TARGET: refctl,
                    callback: function () {
                      tipCallback(refctl.dom)
                    }
                  })
                  doStructCmd(ctl, evt, ev)
                } else
                  tipCallback(refctl.dom)
              }
            },
            cache: false,
            skin: 'light',
            radius: false,
            padding: false,
            size: 'large',
            onShow: function (content, element) {
            },
            afterHide: function (content, element) {
              var refctl = runtime.ctls[ref];
              refctl.dom.appendTo(refctl.container);
              refctl.dom.hide();
            }
          })
        }
        // options.hideOn = ishideOn ? false : {element: 'mouseleave', tooltip:'mouseleave'};
        // options.hideOnClickOutside = ishideOn ? true : false;
        Tipped.create(ctl.dom, options);
      }
    }

    registerEvents(ctl, xitemEv);

    if (!$F.isNull(ctl.xData)) {
      // 数据源的序列
      if (dsSerial) {
        if (ctl.xData.length > dsSerial - 1) {
          ctl.setOptions(ctl.xData[dsSerial - 1]);
        }
      }
      // XITEM控件，直接取数值展示
      if (ctl.serial > 0 && ctl.xData.length > ctl.serial - 1)
        ctl.val(ctl.xData[ctl.serial - 1]);
      $.extend(xitemEv, {
        XITEM: {
          ROWDATA: ctl.xData,
          GRIDROW: ctl.xRow + 1,
          GRIDCOL: ctl.serial
        }
      });
    }
    if (ctl.dataProp) {
      // 绑定值消息，当实例改变时，控件同步更新
      instance.bindValue(ctl.dataProp, ctl, function (e) {
        ctl.evt(e);
      }, true);
    }

    // 绑定状态消息，当实例改变时，控件状态更新
    if (ctl.stateProp) {
      instance.bindValue(ctl.stateProp, ctl, function (e) {
        ctl.state(e.data);
      }, true);
    } else {
      ctl.state(getProp(ctl, 'styles', 'state', 0))
    }
    afterCtlRander(ctl);
  }

  function registerEvents(ctl, xitemEv) {
    if (ctl.dataProp) {
      if (ctl.type !== 'TABLE' && ctl.type !== 'PAGER') {
        ctl.bind('change', function () {
          ctl.instance.fireValue(ctl.dataProp, ctl.runtime, ctl.evt(), ctl);
        });
      }
    } else if (ctl.serial > 0) {
      ctl.bind('change', function () {
        xitemEv.XITEM.ROWDATA[ctl.serial - 1] = ctl.val();
      });
    }

    let cmds = ctl.profile.cmds;
    for (let key in cmds) {
      bindEvent(ctl, key, xitemEv);
    }
  }

  function bindEvent(ctl, cmdId, xitemEv) {
    ctl.bind(cmdId, function (e, obj) {
      fireEvent(ctl, cmdId, $.extend(obj, xitemEv));
    })
  }

  function fireEvent(ctl, cmdId, ev) {
    //当async扩展属性是true时候，使用异步方式，直接执行此命令，避免了有些命令（如File.upload）上下文变化后，就执行无效的问题
    if (ctl.async) {
      $F.process(function () {
        doCmd(ctl, cmdId, ev);
      })
    } else {
      doCmd(ctl, cmdId, ev);
    }
  }

  function getValue(instance, propertyName, runtime) {
    if (propertyName.indexOf('.') == -1) {
      // 引用全局变量
      var project = instance.project;
      switch (propertyName.toUpperCase()) {
        case 'EPID':
        case 'C_EPID':
          return $E.staff.epid;
        case 'EPIDTB':
        case 'C_EPIDTB':
          return $E.staff.epidSql;
        case 'STAFFID':
        case 'C_BY':
          return $E.staff.id;
        case 'STAFFNAME':
          return $E.staff.name;
        case 'AGENTID':
          return $E.staff.agentId;
        case 'ROLEGROUPID':
        case 'C_GRP':
          return project.groupId;
        case 'ROLEGROUPNAME':
          return project.groupName;
        case 'ANI':
        case 'DNIS':
        case 'EXTENSION':
          if (project.cwInfo && project.cwInfo.callInfo && project.cwInfo.callInfo.getValue) {
            return project.cwInfo.callInfo.getValue(propertyName.toLowerCase());
          } else
            return '';
        case 'CALLTYPE':
          if (project.cwInfo)
            return project.cwInfo.callType;
          else
            return '';
        case 'CUREVENTID':
          if (project.cwInfo)
            return project.cwInfo.eventGuid;
          else
            return '';
        case 'G_ISSUPER':
          var isSupervisor = parseInt(project.getParam('ISSUPE', 2));
          return ($E.staff.supervisor > isSupervisor) ? 1 : 0;
        case 'G_SFASHA':
          return parseInt(project.getParam('SFASHA', 0));
        default:
          if (propertyName.startWith("OBJ_")) {
            propertyName = propertyName.substring(4);
            return instance.objective.getValue(propertyName);
          } else if (propertyName.startWith("CUST_")) {
            propertyName = propertyName.substring(5);
            return instance.customer.getValue(propertyName);
          }
      }
    } else
      return instance.getValue(propertyName, runtime);
  }

  function exeSql(ctl, cacheId, sqlId, params, valueField, labelField, value) {
    ctl.runtime.sqlQueue[sqlId] = {
      params: params,
      callback: function (data) {
        var paramsStr = $F.display(params);
        if (data.count == -1) {
          $F.err('SQLExec - failed', 'DYN[' + ctl.runtime.struct.id + '] KEY[' + sqlId + '], 控件[' + ctl.type + '@' + ctl.path + '][PARAMS: ' + paramsStr + ']');
          ctl.setOptions([], '');
        } else {
          if ($E.debug)
            console.log('SQLExec - success', 'DYN[' + ctl.runtime.struct.id + '] KEY[' + sqlId + '], 控件[' + ctl.type + '@' + ctl.path + '][PARAMS: ' + paramsStr + ']');
          if (cacheId) {
            // 缓存结果, cacheId必须使用namespace
            if (Object.keys(params).length > 0)
              $F.err('Ctls - exeSql', '附带入参的SQL结果集不应该置入缓存: ' + cacheId); else {
              $F.log('Ctls - exeSql', '置入SQL结果缓存: ' + cacheId)
              ctl.instance.project.cache[cacheId] = data;
            }
          }
          ctl.setOptions(getDataFromSql(data, valueField, labelField), value);
        }
      },
      error: function (message) {
        $F.err('SQLExec - failed', 'DYN[' + ctl.runtime.struct.id + ']: ' + message);
        ctl.setOptions([], '');
      }
    }
  }

  function getDataFromSql(sqlData, valueField, labelField) {
    if (!sqlData) {
      $F.err('Ctls - getDataFromSql', '无效的数据对象');
      return [];
    }
    if (!sqlData.columns) {
      $F.err('Ctls - getDataFromSql', '不合法数据对象', sqlData);
      return [];
    }
    if (sqlData.fields.length > 0 && $.type(sqlData.fields[0]) !== 'array') {
      $F.err('Ctls - getDataFromSql', '不合法数据对象，也许使用了带有主键的全局SQL', sqlData);
      return [];
    }

    var vfIdx,
      lfIdx,
      vfExp,
      lfExp,
      getIndex = function (property) {
        var index = sqlData.columns.indexOf(property.toUpperCase());
        if (index == -1)
          $F.err('Ctls - getDataFromSql', "数据结果中不存在列名: " + property);
        return index;
      };
    if (!valueField) {
      vfIdx = 0;
    } else {
      if (Verify.isDigit(valueField))
        vfIdx = parseInt(valueField);
      else {
        var matches = /^'(.*)'$/.exec(valueField);
        if (matches) {
          vfExp = $F.ognl(matches[1], function (property) {
            if (Verify.isDigit(property))
              return '${' + property + '}';
            else {
              return '${' + getIndex(property) + '}';
            }
          })
        } else {
          vfIdx = getIndex(valueField);
        }
      }
    }

    var exp = false;
    if (!labelField) {
      lfIdx = 1;
    } else {
      if (Verify.isDigit(labelField))
        lfIdx = parseInt(labelField);
      else {
        var matches = /^'(.*)'$/.exec(labelField);
        if (matches) {
          lfExp = $F.ognl(matches[1], function (property) {
            if (Verify.isDigit(property))
              return '${' + property + '}';
            else {
              return '${' + getIndex(property) + '}';
            }
          })
        } else {
          lfIdx = getIndex(labelField);
        }
      }
    }

    var data = [];
    $.each(sqlData.fields, function (i, v) {
      data.push({
        label: (lfExp ? $F.ognl(lfExp, function (index) {
          return v[parseInt(index)];
        }) : v[lfIdx]),
        value: (vfExp ? $F.ognl(vfExp, function (index) {
          return v[parseInt(index)];
        }) : v[vfIdx])
      })
    });
    return data;
  }

  function getDataFromJsonSql(jsonSqlData, valueField, labelField) {
    // console.log('v=' + valueField + ', l=' + labelField, jsonSqlData)
    valueField = valueField.toUpperCase();
    labelField = labelField.toUpperCase();
    if ($.type(valueField) !== 'string' || $.type(labelField) !== 'string') {
      $F.err('Ctls - getDataFromJsonSql', '不合法的字段名');
      return [];
    }
    var data = [];
    if (jsonSqlData) {
      for (var key in jsonSqlData) {
        if (jsonSqlData.hasOwnProperty(key))
          data.push({
            label: Helper.filter(expBean(labelField, jsonSqlData[key])),
            value: expBean(valueField, jsonSqlData[key])
          })
      }
    }
    // console.log(data)
    return data;
  }

  function expBean(exp, bean) {
    var matches = /^'(.*)'$/.exec(exp);
    if (matches) {
      return $F.ognl(matches[1], function (property) {
        return bean[property];
      })
    } else
      return bean[exp];
  }

  function getDataFromProperty(propertyData, vf, lf) {
    //console.log('v=' + vf + ', l=' + lf, propertyData)

    var data = [],
      dataType = $.type(propertyData);
    if (vf == undefined || lf == undefined) {
      // 未指定值字段，使用默认解析
      // 如果为数组，则使用每行数据的第0/1列
      // 如果为对象，则使用对象的value:label模型
      if (dataType == 'array') {
        $.each(propertyData, function (i, v) {
          data.push({
            label: Helper.filter(v[1]),
            value: v[0]
          })
        })
      } else if (dataType == 'object') {
        for (var key in propertyData) {
          if (propertyData.hasOwnProperty(key)) {
            data.push({
              label: Helper.filter(propertyData[key]),
              value: key
            })
          }
        }
      } else
        $F.err('getDataFromProperty', '不合法的数据类型使用在数据源上：' + dataType);
      return data;
    } else if ($.type(vf) == 'number' && $.type(lf) == 'number') {
      // 指定值与显示字段为列号
      if (dataType == 'array') {
        $.each(propertyData, function (i, v) {
          data.push({
            label: Helper.filter(v[lf - 1]),
            value: v[vf - 1]
          })
        })
      } else
        $F.err('getDataFromProperty', '不合法的数据类型使用在数据源上：' + dataType);
      return data;
    } else if ($.type(vf) == 'string' && $.type(lf) == 'string') {
      // 指定值与显示字段为列名
      if (dataType == 'array') {
        $.each(propertyData, function (i, v) {
          data.push({
            label: Helper.filter(v[lf]),
            value: v[vf]
          })
        })
      } else
        $F.err('getDataFromProperty', '不合法的数据类型使用在数据源上：' + dataType);
      return data;
    } else {
      $F.err('getDataFromProperty', '不合法的数据源配置');
      return data;
    }
  }

  function getPowerPersenalize(runtime, id) {
    var powers = runtime._POWER;
    if (powers) {
      return powers[id];
    }
    return null;
  }

  class CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      const a = this;
      a.id = profile.id + (rowNum == undefined ? '' : ('-' + rowNum));
      a.eid = profile.eid;
      a.type = profile.name.toUpperCase();
      a.instance = runtime.instance;
      a.container = container;
      a.runtime = runtime;
      a.project = runtime.project;
      a.namespace = path || runtime.namespace;
      a.path = runtime.namespace + '.' + a.id;
      a.profile = profile;
      a.xRow = rowNum;
      a.xData = data;
      a.widgetState = 0;
      a.attributes = localPro(a, 'attributes', {});
      a.methods = {};
      a.container = container;

      profile.props = profile.props || {};
      profile.props.styles = profile.props.styles || {};
      profile.props.general = profile.props.general || {};
      profile.props.local = profile.props.local || {};
      profile.props.extend = profile.props.extend || {};

      beforeRender(a);
      a.dataProp = getProp(a, 'general', 'dataProp');
      a.serial = getProp(a, 'general', 'serial');
      a.dsSerial = getProp(a, 'general', 'dsSerial');
      a.stateProp = getProp(a, 'general', 'stateProp');
      return this;
    }

    state(e) {
      var a = this;
      if (e == undefined)
        return a.widgetState;
      a.widgetState = e;
      if (e == 1)
        a.dom.hide();
      else {
        a.dom.show();
        a.disable(e == 2);
      }
    }

    hide() {
      this.state(1)
    }

    show() {
      this.state(0)
    }

    disable(e) {
      if (this.dom)
        this.dom.toggleClass('disabled', e);
      if (this.input) {
        if (this.input.wisdgeCtl)
          this.input.disable(e);
        else {
          this.input.prop('disabled', e);
        }
      }
    }

    css(e) {
      if (this.input && !this.input.wisdgeCtl)
        this.input.css(e);
      else
        this.dom.css(e);
    }

    val(e) {
      if (e === undefined)
        return this.value;

      this.value = e;
    }

    attr() {
      this.attributes = this.attributes || {};
      if (arguments.length > 1) {
        this.attributes[arguments[0]] = arguments[1];
      } else {
        return this.attributes[arguments[0]];
      }
    }

    getItem(e) {
      return this.attributes[e];
    }

    setItem(e, v) {
      this.attributes[e] = v;
    }

    storeMethod(e, callback) {
      this.methods[e] = callback || e;
    }

    doMethod(e) {
      let a = this, callback = a.methods[e];
      if ($.isFunction(callback)) {
        return callback.call(a);
      } else if ($.type(callback) == 'string') {
        doStructCmd(a, callback, {CTRL: a});
      }
    }

    evt(e) {
      if (!arguments.length) {
        return {
          type: 0,
          data: this.val()
        }
      } else {
        this.val(e.data, e.unbind);
      }
    }

    bind(e, callback) {
      // console.log('Bind event: ' + e)
      if (this.input && !this.input.wisdgeCtl)
        this.input.bind(e, callback);
      else
        this.dom.bind(e, callback);
    }

    bindProperty(propertyName) {
      // 绑定了变量的控件，值改变时触发消息通知给实例
      var a = this;
      propertyName = propertyName.toLowerCase();
      a.bind('change', function (e) {
        a.instance.fireValue(propertyName, a.runtime, a.evt(), a);
      });

      // 绑定值消息，当实例改变时，控件同步更新
      a.instance.bindValue(propertyName, a, function (e) {
        a.evt(e);
      }, true);
    }

    trigger(e) {
      if (this.input && $.isFunction(this.input.trigger))
        this.input.trigger(e)
    }

    /**
     * 获取某个属性的值，或者给某个属性赋值
     * @param scope {string} 属性类别
     * @param key {string} 属性名称
     * @param [value] {any} 属性值
     */
    prop(scope, key, value) {
      var a = this;

      if (arguments.length < 3)
        return getProp(a, scope, key);

      a.profile.props = a.profile.props || {};
      a.profile.props[scope] = a.profile.props[scope] || {};
      a.profile.props[scope][key] = value;
      return a;
    }

    focus() {
      if (this.input) {
        try {
          this.input.focus();
        } catch (e) {
        }
      }
      return this;
    }

    select() {
      if (this.input)
        this.input.select();
      return this;
    }

    toggle() {
      if (this.widgetState)
        this.state(0);
      else
        this.state(1);

      if (this.stateProp)
        a.instance.fireValue(this.stateProp, a.runtime, {
          type: 0,
          data: this.widgetState
        }, a);
      return this;
    }

    tooltip(e, direct) {
      var a = this;
      if (e == undefined)
        return a.dom.attr('title');

      a.dom.attr('title', e);
      if (direct)
        Tipped.create(a.input, {
          position: direct
        });
      else
        Tipped.create(a.input)
    }

    data() {
      if (arguments.length == 0)
        return this.attributes;

      if (arguments.length < 2)
        return this.attributes[arguments[0]];

      this.attributes[arguments[0]] = arguments[1];
    }

    driftLayer(layer, options) {
      var owner = (options && options.source) || this.input || this.dom;
      if ($.isFunction(layer.drift)) // 传入当前控件的屏幕绝对坐标
        layer.drift(owner.offset().top,
          owner.offset().left,
          owner.offset().top + owner.outerHeight(),
          owner.offset().left + owner.outerWidth(), options);
      else
        $F.err('Ctls - driftLayer', '控件[' + layer.id + ']不支持浮动');
    }

    verify() {
      return true;
    }

    resetValidation() {
      if (this.ctls) {
        this.ctls.forEach(function (ctl) {
          ctl.resetValidation()
        })
      }
      if (this.inputBox) {
        $('.detail', this.inputBox).html('&nbsp;')
        this.form && this.form.removeClass('ve_input_error')
      }
    }

    reset() {
      if (this.ctls) {
        this.ctls.forEach(function (ctl) {
          ctl.reset()
        })
      }
    }

    destroy(live) {
      if (this.input)
        Tipped.remove(this.input);
      if (this.dom) {
        Tipped.remove(this.dom);
        this.dom.empty().html('');
        if (!live)
          this.dom.remove();
      }
    }

    /**
     * 修改带label 控件的label部分
     * error--message: 这方法有问题， label是对象属性， 同名于属性的方法是无法调用的
     * @param e
     * @returns {CtlBase|*|string}
     */
    label(e) {
      var a = this;
      if (e === undefined)
        return a.label ? a.label.text() : '';

      a.label && a.label.text(e);
      return a;
    }

    /**
     * 修改带label 控件的label部分
     * @param e
     * @returns {CtlBase|*|string}
     */
    labelText(e) {
      var a = this;
      if (e === undefined)
        return a.label ? a.label.text() : '';

      a.label && a.label.text(e);
      return a;
    }

    /**
     * 修改带label 控件的label部分(支持富文本)
     * @param e
     * @returns {string|*}
     */
    labelHtml(e) {
      if (this.label) {
        if (e) {
          this.label.html(e);
          return '';
        } else {
          return this.label.html()
        }
      }
      return '';
    }

    html(e) {
      var a = this;
      if (a.input && !a.input.wisdgeCtl)
        a.input.html(e);
      else
        a.dom.html(e);
    }

    /**
     * 控件行为跟踪
     * @param event
     */
    trace(event) {
      if (this.runtime.struct.local && this.runtime.struct.local.trace) {
        let label = event.label || '';
        if (!label) {
          if (this.label && $.isFunction(this.label.text)) {
            label = this.label.text();
          }
        }
        this.project.addTrace(this.path, label, 1, event);
      }
    }
  }

  /*
	 * 动态页面组件：Input
	 */
  class CtlInput extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a),
        attrs = '',
        maxLength = localPro(a, 'maxLength'),
        lbStyle = localPro(a, 'lbStyle'),
        labelStr = localPro(a, 'label', '', true),
        assist = localPro(a, 'assist');

      a.placeholder = localPro(a, 'placeholder', '', true);
      a.showDetail = localPro(a, 'detail');
      a.inputType = localPro(a, 'type', 0);

      if (localPro(a, 'rtl'))
        skin.clazzs.push('dyn_rtl');
      if (a.placeholder)
        attrs += ' placeholder="' + a.placeholder + '"';
      if (localPro(a, 'required'))
        attrs += ' required="required"';
      if (maxLength)
        attrs += ' maxLength=' + maxLength;
      if (skin.styles['color']) {
        skin.inputStyles['color'] = skin.styles['color'];
        delete skin.styles['color'];
      }
      if (skin.styles['backgroundColor']) {
        skin.inputStyles['backgroundColor'] = skin.styles['backgroundColor'];
        delete skin.styles['backgroundColor'];
      }
      if (skin.styles['padding']) {
        skin.inputStyles['padding'] = skin.styles['padding'];
        delete skin.styles['padding'];
      }
      if (a.inputType < 5 || a.inputType > 6) {
        delete skin.styles.height;
      }

      if (a.inputType < 6 || a.inputType == 7) {
        if (labelStr) {
          // 带标签控件
          a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
          a.form = $('<div class="ve_form" />').appendTo(a.dom);
          a.labelBox = $('<div class="labelBox" />').appendTo(a.form);
          a.label = $('<span class="ve_field" />').html(labelStr).appendTo($('<div class="label"><span class="ve_z"></span></div>').appendTo(a.labelBox));
          if (a.showDetail)
            a.labelBox.append('<div class="detail">&nbsp;</div>');
          a.inputBox = $('<div class="inputBox" />').appendTo(a.form);
          if (a.inputType == 3)
            a.input = $('<input type="password" size=0 class="ve_input" ' + attrs + '/>').css(skin.inputStyles).appendTo(a.inputBox);
          else if (a.inputType == 7)
            a.input = $('<input type="file" size=0 class="ve_input" ' + attrs + '/>').css(skin.inputStyles).appendTo(a.inputBox);
          else if (a.inputType > 4)
            a.input = $('<textarea class="ve_input" ' + attrs + '/>').css(skin.inputStyles).appendTo(a.inputBox);
          else
            a.input = $('<input type="text" size=1 class="ve_input" ' + attrs + ' autocomplete="off" />').css(skin.inputStyles).appendTo(a.inputBox);
          if (assist || a.inputType == 2 || a.inputType == 4)
            a.form.addClass("ve_inner");
          if (a.showDetail)
            a.inputBox.append('<div class="detail">&nbsp;</div>');

          if (lbStyle) {
            if (lbStyle.a) {
              if (lbStyle.a == 'justify') {
                skin.labelStyles['textAlignLast'] = lbStyle.a;
              } else {
                skin.labelStyles['textAlign'] = lbStyle.a;
              }
            }
            if (lbStyle.w)
              skin.labelStyles['width'] = lbStyle.w;
            if (lbStyle.c)
              skin.labelStyles['color'] = lbStyle.c;
          }
          a.label.css(skin.labelStyles)
        } else {
          // 无标签控件
          a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
          if (assist || a.inputType == 2 || a.inputType == 4 || skin.styles['display'] == 'block') {
            // 带有右侧帮助按钮（或者颜色/联想）
            a.form = $('<div class="ve_inner" />').appendTo(a.dom);
          } else {
            a.form = a.dom;
          }
          a.inputBox = $('<div class="inputBox" />').appendTo(a.form);
          if (a.inputType == 3)
            a.input = $('<input type="password" class="ve_input" ' + attrs + '/>').css(skin.inputStyles).appendTo(a.inputBox);
          else if (a.inputType > 4)
            a.input = $('<textarea class="ve_input" ' + attrs + '/>').css(skin.inputStyles).appendTo(a.inputBox);
          else
            a.input = $('<input type="text" class="ve_input" ' + attrs + ' autocomplete="off" />').css(skin.inputStyles).appendTo(a.inputBox);
          if (a.showDetail)
            a.inputBox.append('<div class="detail">&nbsp;</div>');
        }
        a.input.css(skin.inputStyles)
        if (a.inputType == 5 && skin.styles['height'] == 'auto') {
          // 多行输入且设置高度为'auto'时，根据内容进行高度扩展
          a.input.on('change', function (e) {
            a.input.css('height', '2.2em').css('height', (a.input[0].scrollHeight + 8) + "px");
          }).on('keydown', function (e) {
            if (e.keyCode == 13) {
              a.input.css('height', '2.2em').css('height', (a.input[0].scrollHeight + 8) + "px");
            }
          }).css({
            'minHeight': '2.2em'
          }).data('autoScroll', true)
        }
        if (assist) {
          // 配置了右侧引导帮助
          a.assist = $('<i class="ibtn ' + $F.fas(assist.icon) + ' fa-fw"></i>')
            .appendTo($('<span class="ve_assist"><span class="ve_z"></span></span>').appendTo(a.inputBox))
            .click(function (e) {
              e.preventDefault();
              if (a.input.prop('disabled') || a.input.prop('readonly')) {
                return;
              }
              doStructCmd(a, assist.cmd, {
                INPUT: a.input,
                VALUE: a.input.val()
              });
            })
          if (assist.readonly) {
            a.input.on('input', function (e) {
              e.preventDefault();
              e.stopPropagation();
            })
            a.input.attr('onpaste', 'return false');
            a.input.attr('oncontextmenu', 'return false');
            a.input.on('keydown', function (e) {
              e.preventDefault();
              e.stopPropagation();
              if (e.keyCode == 46 || e.keyCode == 8)
                a.input.val('').change();
            })
          }
          if (assist.autoassist) {
            a.input.bind('click', function (e) {
              a.assist.click();
            })
          }
        } else if (a.inputType == 2) {
          // 颜色输入
          a.input.bind('blur', function (e) {
            if (a.input.val() !== a.value)
              a.input.trigger('change');
          })
          a.assist = $('<i class="ibtn far fa-adjust fa-fw"></i>')
            .appendTo($('<span class="ve_assist"><span class="ve_z"></span></span>').appendTo(a.inputBox))
            .click(function (e) {
              e.preventDefault();
              if (a.input.prop('disabled') || a.input.prop('readonly'))
                return;
              a.input.css('background', '#FFFFFF');
              a.input.val('').trigger('change');
            })
          H5Utils.colorPicker(a.input);
        }
      } else if (a.inputType === 6) {
        // 富文本输入
        a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
        a.form = a.dom;
        a.input = $('<textarea ' + attrs + ' style="width:100%;height:100%" />').css(skin.inputStyles).appendTo(a.form);
        const busyId = $E.progressing || $E.silent ? '' : $F.busyShow();
        H5Utils.initCkEditor(function () {
          a.richCfg = localPro(a, 'rich', {});
          a.createRichEditor(function () {
            $F.busyHide(busyId);
          })
        })
      }

      if (a.inputType === 1) {
        // 数字输入
        var formatOptions = localPro(a, 'format', {});
        if ($E.currencyStyle)
          formatOptions.lang = 'fr';
        else
          delete formatOptions.lang;
        if (maxLength) {
          formatOptions.maxLength = maxLength
        }
        a.input.formatNumber(formatOptions);
        a.input.on('focus', function () {
          a.input.select();
        })
        //Helper.makeNumberInput(a.input, localPro(a, 'zero'), localPro(a, 'format'))
      } else if (a.inputType === 4) {
        // 联想输入
        if (!assist) {
          // 未配置右侧引导帮助
          a.assist = $('<i class="ibtn fab fa-mixcloud fa-fw"></i>')
            .appendTo($('<span class="ve_assist"><span class="ve_z"></span></span>').appendTo(a.inputBox))
            .click(function (e) {
              e.preventDefault();
              if (a.input.prop('disabled') || a.input.prop('readonly')) {
                return;
              }
              a.input.data('value', '');
              a.input.focus();
            })
        }

        a.associate = localPro(a, 'associate');
        if (a.associate) {
          if (a.associate.nl) {
            a.input.on('input', function (e) {
              a.input.removeData('value');
              a.input.removeData('data');
            })
          }
          a.input.autocomplete({
            classes: {
              "ui-autocomplete": "highlight"
            },
            create: function (event, ui) {
              if (a.associate.ml == -1) {
                a.input.on('keyup', function (e) {
                  if (e.keyCode == 13) {
                    a.input.autocomplete("instance")._search(a.input.val())
                  }
                })
              }
            },
            source: function (request, response) {
              if (a.associate.type) {
                // 命令模式
                //request.term.length = request.term.length - 1;
                var ac = {
                  FIND: request.term,
                  RESULT: []
                };
                doStructCmd(a, a.associate.cmd, ac);

                var ar = [];
                $.each(ac.RESULT, function (i, v) {
                  if ($.type(v) == 'string') {
                    ar.push({
                      label: v,
                      value: v,
                      data: v
                    });
                  } else if ($.type(v) == 'array') {
                    ar.push({
                      label: v[1],
                      value: v[0],
                      data: v
                    })
                  } else {
                    ar.push({
                      label: v.key,
                      value: v.value,
                      data: v
                    })
                  }
                })
                response(ar);
              } else {
                // 全局缓存模式
                var vf = a.associate.vf.toUpperCase(),
                  lf = a.associate.lf.toUpperCase(),
                  ar = [];
                var jsonSql = $F.ognl(a.associate.sql || '', function (propertyName) {
                  if (propertyName.toUpperCase() == 'SEARCH')
                    return request.term;
                  else
                    return a.instance.getValue(propertyName, a.runtime);
                })
                var data = a.instance.project.queryCodetable(a.associate.key, jsonSql);
                $.each(data, function (i, v) {
                  ar.push({
                    value: v[vf],
                    label: v[lf],
                    data: v
                  })
                })
                response(ar);
              }
            },
            minLength: (a.associate.ml || 999),
            select: function (event, ui) {
              if (ui.item) {
                a.val(ui.item.label);
                a.input.data('value', ui.item.value);
                a.input.data('data', ui.item.data);
                a.input.trigger('change');
                if (!a.associate.nl) {
                  a.input.val(ui.item.label);
                }
                return false;
              }
            }
          });
        }
      } else if (a.inputType === 0) {
        // 格式化
        var format = localPro(a, 'format2')
        if (format) {
          format = $F.ognl(format.trim(), function (propertyName) {
            return a.instance.getValue(propertyName, a.runtime);
          })
          a.input.mask(format);
        }
      }

      var entercmd = localPro(a, 'enter');
      // if (entercmd) {
      a.input.on('keydown', function (e) {
        if (e.keyCode == 13) {
          a.input.trigger('change');
          if (entercmd) {
            doStructCmd(a, entercmd, {
              INPUT: a.input,
              VALUE: a.input.val()
            });
            e.preventDefault();
          }
          e.stopPropagation();
        }
      })
      // }

      if (maxLength && a.inputType !== 4) {
        a.maxLengthDom = $('<div class="ve_maxlength">&nbsp;</div>');
        a.input.on('focus', function () {
          $('.detail', a.inputBox).empty().append(a.maxLengthDom)
          a.maxLengthDom.html('还可以输入<span>' + (maxLength - a.input.val().length) + '</span>字')
        })
        a.input.on('input', function () {
          a.maxLengthDom.html('还可以输入<span>' + (maxLength - a.input.val().length) + '</span>字')
          // a.input.trigger('change')
        })
        a.input.on('blur', function () {
          a.maxLengthDom.html('&nbsp;')
        })
      }
      a.input.on('change blur', function () {
        if (a.inputType !== 4) {
          a.verify();
        }
      })
      // a.input.on('blur', function() {
      // 			// 	a.verify()
      // 			// 	// a.input.trigger('change')
      // 			// })
      // 鼠标抬起事件，可以获取当前选中的字符
      var mouseupcmd = localPro(a, 'mouseup');
      if (mouseupcmd) {
        // 鼠标点下时候开始
        a.input.on('mousedown', function (e) {
          // 绑定一个全局一次的鼠标抬起事件回调，避免划词到输入框范围外后，不触发输入框本身的mouseup的问题
          $(document).one('mouseup', function (e) {
            setTimeout(function () {
              var selection = window.getSelection().toString();
              doStructCmd(a, mouseupcmd, {
                SELECTION: selection,
                SELECTIONSTART: a.input[0].selectionStart,
                SELECTIONEND: a.input[0].selectionEnd,
                VALUE: a.input.val()
              })
            }, 10);
          });
        });
      }

      makeValueAndState(a);
    }

    state(e) {
      const a = this;
      if (e == undefined)
        return this.widgetState;
      this.widgetState = e;
      if (e == 1)
        this.dom.hide(); else {
        this.dom.show();
        this.disable(e == 2);
      }
      this.input.prop('readonly', (e == 3));
      if (this.form)
        this.form.toggleClass('readonly', e == 3);

      if (a.inputType === 1 && e === 2) {
        const formatOptions = localPro(this, 'format', {});
        if (formatOptions.disnone) {
          this.input.attr('placeholder', '--')
          this.input.val('')
        } else {
          this.input.attr('placeholder', this.placeholder)
        }
      }
    }

    val(e) {
      let v;
      const a = this,
        maxLength = localPro(a, 'maxLength');
      if (!arguments.length) {
        if (a.inputType == 1) {
          v = a.input.val();
          if (!v) {
            // console.log('zero: ' + localPro(a, 'zero', 0))
            return (localPro(a, 'zero', 0) ? 0 : '');
          } else
            return parseFloat(v);
        } else if (a.inputType == 4) {
          v = a.input.data('value');
          if (a.associate && a.associate.nl) {
            a.instance.fireValue(a.associate.nl, a.runtime, {
              type: 0,
              data: v
            }, a)
            return a.input.val();
          } else {
            if (!v) {
              a.input.val('');
              return '';
            }
            if (a.input.val() === '') {
              a.input.data('value', '');
              return '';
            }
            return v;
          }
        } else if (a.inputType == 7) {
          return a.input[0].files[0];
        } else {
          v = a.input.val();
          if (maxLength && v) {
            v = v.substr(0, maxLength);
            if (v != a.input.val())
              a.input.val(v);
            return a.instance.getValueByPropertyName(a.runtime, a.dataProp, v.toString());
          } else
            return v;
        }
      } else {
        if (a.inputType == 7)
          return a;

        if (localPro(a, 'mask')) {
          let mask = localPro(a, 'maskStr');
          if (mask) {
            mask = $F.ognl(mask.trim(), function (propertyName) {
              return a.instance.getValue(propertyName, a.runtime);
            })
            a.input.val($F.shieldMask(e, mask));
          } else
            a.input.val(a.instance.project.shieldString(e))
        } else {
          a.input.val(e);
        }

        if (a.inputType == 5) {
          // 对于包含auto height的风格名的多行输入框，自动高度
          if (a.input.data('autoScroll')) {
            a.input.css('height', '2.2em').css('height', (a.input[0].scrollHeight + 8) + "px");
          }
        } else if (a.inputType == 6) {
          // 富文本
          if (a.ckeditor) {
            setTimeout(function () {
              a.ckeditor.resetDirty();
              a.ckeditor.setData(e);
            }, 10)
          } else {
            a.input.val(e);
          }
        } else if (a.inputType == 4) {
          // 联想
          a.input.data('value', e);
          a.input.data('label', e);
          // } else if (a.inputType == 1) {
          // Fixed: by kevin
          // don't do this, it will cause trigger change event，some change
          // 	a.input.trigger('blur');
        }
      }
      return a;
    }

    verify() {
      const a = this;
      if (super.verify()) {
        if (a.widgetState == 1) return true;
        try {
          let value;
          if (a.inputType === 1) {
            value = a.val();
          } else if (a.inputType === 4 && a.associate && a.associate.nl) {
            let index = String(a.associate.nl).indexOf('.')
            a.input.data().value = String(a.runtime.data.pub[a.associate.nl.slice(0,index)][a.associate.nl.slice(index + 1)]).trim();
            value = String(a.val()).trim();
          } else {
            value = String(a.val()).trim();
          }
          if (localPro(a, 'required') && !value && value !== 0) {
            a.input.val('');
            const tip = localPro(a, 'required', '', 1) + $F.lang('sys.invalid');
            if (a.showDetail)
              $('.detail', a.inputBox).text(tip)
            else {
              $F.notify(tip, -1);
            }
            a.form.addClass('ve_input_error')
            return false;
          } else {
            $('.detail', a.inputBox).html('&nbsp;')
            a.form.removeClass('ve_input_error')
          }
          const pattern = localPro(a, 'pattern');
          if (pattern) {
            // '/^[a-zA-Z]{1}[-_a-zA-Z0-9]{5,19}$/'
            const regex = eval(pattern.regex);
            if (!regex.test(a.val())) {
              if (a.showDetail)
                $('.detail', a.inputBox).text(pattern.tip || ('验证未通过：' + regex))
              else {
                $F.notify(pattern.tip || ('正则验证未通过：' + regex), -1);
              }
              a.form.addClass('ve_input_error')
              return false;
            } else {
              $('.detail', this.inputBox).html('&nbsp;')
              a.form.removeClass('ve_input_error')
            }
          }
          return true;
        } catch (e) {
          console.error(e);
        }
      }
      return false;
    }

    reset() {
      super.reset()
      // this.input.reset()
      const a = this;
      $('.detail', a.inputBox).html('&nbsp;')
      a.form.removeClass('ve_input_error')
    }

    commit() {
      var a = this;
      if (a.inputType == 6) {
        this.input.val(this.ckeditor.getData()).trigger('change');
      }
    }

    redraw() {
      var a = this;
      if (a.inputType == 6) {
        if (a.ckeditor) {
          a.ckeditor.destroy();
          a.createRichEditor();
        }
      }
    }

    createRichEditor(callback) {
      const a = this,
        mode = a.richCfg.mode || 0,
        filePath = a.richCfg.filePath || '/fs/uploadFile',
        imgPath = a.richCfg.imgPath || '/fs/uploadImg',
        pasteImgPath = (mode == 2) ? '/fs/forbidden' : (a.richCfg.pastePath || '/fs/uploadPDImg'),
        captureImgPath = a.richCfg.capturePath || '/fs/uploadCapture';

      const getUrl = function (url) {
        const u = a.instance.ognl(url, a.runtime);
        if (/^(http)/gi.test(u))
          return u;
        return $E.url + u;
      }
//			CKEDITOR.config.filebrowserUploadUrl = getUrl(filePath);
//			CKEDITOR.config.filebrowserImageUploadUrl = getUrl(imgPath);
//			CKEDITOR.config.imageUploadUrl = getUrl(pasteImgPath);
//			CKEDITOR.config.captureUploadUrl = getUrl(captureImgPath);
      var config = 'config_stand.js?v=' + $E.ver;
      if (mode == 1)
        config = 'config.js?v=' + $E.ver;
      else if (mode == 2)
        config = 'config_simple.js?v=' + $E.ver;
      else if (mode == 3)
        config = 'config_full.js?v=' + $E.ver;

      let lang = $E.lang || 'zh_CN';
      if (lang === 'zh_CN') {
        lang = 'zh-cn';
      } else {
        lang = lang.split('_')[0];
      }
      // console.log(lang)
      a.ckeditor = CKEDITOR.replace(a.input[0], {
        language: lang,
        filebrowserBrowseUrl: getUrl(filePath),
        filebrowserUploadUrl: getUrl(imgPath),
        imageUploadUrl: getUrl(pasteImgPath),
        captureUploadUrl: getUrl(captureImgPath),
        customConfig: $E.url + "/js/ckeditor/" + config,
        on: {
          instanceReady: function (ev) {
            this.container.addClass('elite');
            this.container.$.tabIndex = 0;
            this.on('blur', function () {
              this.container.removeClass('focus');
              if (a.input) { // 避免blur瞬间关闭了当前页面造成的报错
                if (this.checkDirty()) {
                  a.input.val(this.getData()).trigger('change')
                }
                a.verify()
              }
            });
            this.on('focus', function () {
              this.container.addClass('focus');
            });
            this.setData(a.input.val())
            a.input.data('ckeditor', a).forceClass('dyn_ckeditorInput')
            if (callback)
              callback()
          }
        }
      })
    }
  }

  /*
	 * 动态页面组件：Numbox
	 */
  class CtlNumbox extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a),
        attrs = '',
        placeholder = localPro(a, 'placeholder', '', true),
        max = localPro(a, 'max'),
        min = localPro(a, 'min');

      if (placeholder)
        attrs += ' placeholder="' + placeholder + '"';
      if (localPro(a, 'required'))
        attrs += ' required="required"';
      if (skin.styles['color']) {
        skin.inputStyles['color'] = skin.styles['color'];
        delete skin.styles['color'];
      }
      if (skin.styles['backgroundColor']) {
        skin.inputStyles['backgroundColor'] = skin.styles['backgroundColor'];
        delete skin.styles['backgroundColor'];
      }

      // 无标签控件
      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.form = $('<div class="ve_inner" />').appendTo(a.dom);
      var box = $('<div class="ve_widget_numbox numbox"><div class="minus border radius"><span>-</span></div></div>').appendTo(a.dom);
      a.input = $('<input type="text" class="alignment ve_input" style="color:#838383;height:100%;min-height:2.2em" placeholder="数字" readonly />').appendTo(box);
      box.append('<div class="plus border radius"><span>+</span></div>');
      a.input.css(skin.inputStyles)
      a.dom.append('<div class="eliteDisabledMasker"/>');

      var entercmd = localPro(a, 'enter');
      if (entercmd) {
        a.input.on('keydown', function (e) {
          if (e.keyCode == 13) {
            doStructCmd(a, entercmd, {
              INPUT: a.input,
              VALUE: a.input.val()
            })
            e.stopPropagation();
          }
        })
      }

      Helper.makeNumberInput(a.input);
      a.input.on('input', function (e) {
        setTimeout(function () {
          //console.log(a.input.val().length);
          var v = parseInt(a.input.val());
          if (a.max !== undefined && v > a.max)
            a.input.val(a.max);
          if (a.min !== undefined && v < a.min)
            a.input.val(a.min);
        }, 0)
      })

      $('.numbox>div', a.dom).on('click', function (e) {
        var x = parseInt(a.input.val());
        if (isNaN(x))
          x = 0;
        if ($(e.currentTarget).hasClass('plus'))
          x++;
        else
          x--;
        if (a.max)
          x = Math.min(a.max, x);
        if (a.min)
          x = Math.max(a.min, x);
        a.input.val(x).trigger('change');
      })

      makeValueAndState(a);
    }

    state(e) {
      if (e == undefined)
        return this.widgetState;
      this.widgetState = e;
      if (e == 1)
        this.dom.hide(); else {
        this.dom.show();
        this.disable(e == 2);
      }
      this.input.prop('readonly', (e == 3));
      if (this.form)
        this.form.toggleClass('readonly', e == 3);
    }

    val(e) {
      const a = this;
      if (e === undefined) {
        const v = a.input.val();
        if (!v)
          return 0;
        else
          return parseFloat(v);
      } else {
        if (a.min !== undefined)
          e = Math.max(a.min, e);
        if (a.max !== undefined)
          e = Math.min(a.max, e);
        a.input.val(e);
      }
    }

    reset() {
      super.reset()
      // this.input.reset()
    }
  }

  /*
	 * 动态页面组件：Datetime
	 */
  class CtlDatetime extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a),
        attrs = '',
        lbStyle = localPro(a, 'lbStyle'),
        labelStr = localPro(a, 'label', '', true),
        dateType = localPro(a, 'datetype', 'yyyy-MM-dd HH:mm');
      a.showDetail = localPro(a, 'detail')
      a.dateType = dateType.replace(/yyyy/g, 'Y')
        .replace(/MM/g, 'm')
        .replace(/dd/g, 'd')
        .replace(/HH/g, 'H')
        .replace(/hh/g, 'h')
        .replace(/mm/g, 'i')
        .replace(/ss/g, 's');

      if (localPro(a, 'required'))
        attrs += ' required="required"';
      if (skin.styles['color']) {
        skin.inputStyles['color'] = skin.styles['color'];
        delete skin.styles['color'];
      }
      if (skin.styles['backgroundColor']) {
        skin.inputStyles['backgroundColor'] = skin.styles['backgroundColor'];
        delete skin.styles['backgroundColor'];
      }

      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.form = $('<div class="ve_inner" />').appendTo(a.dom);
      if (labelStr) {
        // 带标签控件
        a.form.addClass("ve_form");
        a.labelBox = $('<div class="labelBox" />').appendTo(a.form);
        a.label = $('<span class="ve_field" />').html(labelStr).appendTo($('<div class="label"><span class="ve_z"></span></div>').appendTo(a.labelBox));
        if (lbStyle) {
          if (lbStyle.a) {
            if (lbStyle.a == 'justify') {
              skin.labelStyles['textAlignLast'] = lbStyle.a;
            } else {
              skin.labelStyles['textAlign'] = lbStyle.a;
            }
          }
          if (lbStyle.w)
            skin.labelStyles['width'] = lbStyle.w;
          if (lbStyle.c)
            skin.labelStyles['color'] = lbStyle.c;
        }
        a.label.css(skin.labelStyles)
        if (a.showDetail)
          a.labelBox.append('<div class="detail">&nbsp;</div>');
      }
      var placeholder = a.profile.props.local.placeholder ? a.profile.props.local.placeholder : '';
      a.inputBox = $('<div class="inputBox" />').appendTo(a.form);
      a.input = $('<input type="text" class="ve_input" ' + attrs + ' style="ime-mode:disabled" placeholder="' + placeholder + '"/>').appendTo(a.inputBox);
      a.input.css(skin.inputStyles);
      if (a.showDetail)
        a.inputBox.append('<div class="detail">&nbsp;</div>');

      let timepickerVisible = (a.dateType.indexOf('H') !== -1),
        datepickerVisible = (a.dateType.indexOf('Y') !== -1);
      a.input
        .validateDatetime(a.dateType)
        .on('blur', function () {
          let o = a.input.val();
          if (o !== a.value) {
            a.value = o;
            a.input.trigger('change');
          }
        })
        .data('shell', a.input.parent());
      // if (a.project.params['DPNOIN']) {
      a.input.bind('keydown', function (e) {
        if (a.project.params['DPNOIN']) {
          e.stopImmediatePropagation();
          e.preventDefault();
          if (e.keyCode === 46 || e.keyCode === 8) {
            a.input.val('').trigger('change');
          }
        } else {
          if (e.keyCode === 13) {
            a.input.trigger('change');
            e.preventDefault();
            e.stopPropagation();
          }
        }
      })
      // }

      a.assist = $('<i class="ibtn far fa-calendar-alt fa-fw"></i>')
        .appendTo($('<span class="ve_assist"><span class="ve_z"></span></span>').appendTo(a.inputBox))
        .click(function (e) {
          e.preventDefault();
          if (a.input.prop('disabled') || a.input.prop('readonly'))
            return;

          a.input.datetimepicker({
            zIndex: 100002,
            fixed: true,
            showOnFocus: false,
            format: a.dateType,
            exactTime: true,
            datepicker: datepickerVisible,
            timepicker: timepickerVisible,
            locale: $E.lang
          });
          $.datetimepicker.setLocale($E.lang)
        })
      makeValueAndState(a);
    }

    state(e) {
      if (e == undefined)
        return this.widgetState;
      this.widgetState = e;
      if (e == 1)
        this.dom.hide(); else {
        this.dom.show();
        this.disable(e == 2);
      }
      this.input.prop('readonly', (e == 3));
      if (this.form)
        this.form.toggleClass('readonly', e == 3);
    }

    val(e) {
      var a = this;
      if (!arguments.length) {
        var x = a.value;
        if (!x)
          return '';
        return $F.formatDate(x, a.dateType);
      }

      if (!e) {
        a.input.val('');
        a.value = '';
        return;
      }
      e = $F.parseDate(e);
      if (e.getFullYear() == 1900) {
        if (a.dataProp)
          a.instance.fireValue(a.dataProp, a.runtime, {
            type: 0,
            data: ''
          }, a);
        a.input.val('');
        a.value = '';
        return;
      }

      e = e.format2(a.dateType);
      a.input.val(e);
      a.value = e;
    }

    verify() {
      const a = this;
      if (super.verify()) {
        if (a.widgetState == 1) return true;
        try {
          if (localPro(a, 'required') && !a.val()) {
            const tip = localPro(a, 'required', '', 1) + $F.lang('sys.invalid');
            if (a.showDetail)
              $('.detail', a.inputBox).text(tip)
            else {
              $F.notify(tip, -1);
            }
            a.form.addClass('ve_input_error')
            return false;
          } else {
            $('.detail', a.inputBox).html('&nbsp;')
            a.form.removeClass('ve_input_error')
          }
          return true
        } catch (e) {
          console.error(e);
        }
      }
      return false;
    }

    reset() {
      const a = this;
      super.reset()
      // this.input.reset()
      $('.detail', a.inputBox).html('&nbsp;')
      a.form.removeClass('ve_input_error')
    }

    destroy() {
      if (this.input && this.input.datetimepicker)
        this.input.datetimepicker('destroy');
      super.destroy();
    }
  }

  /*
	 * 动态页面组件：YearMonth
	 */
  class CtlYearMonth extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a),
        attrs = '',
        lbStyle = localPro(a, 'lbStyle'),
        labelStr = localPro(a, 'label', '', true);
      a.showDetail = localPro(a, 'detail')

      if (localPro(a, 'required'))
        attrs += ' required="required"';
      if (skin.styles['color']) {
        skin.inputStyles['color'] = skin.styles['color'];
        delete skin.styles['color'];
      }
      if (skin.styles['backgroundColor']) {
        skin.inputStyles['backgroundColor'] = skin.styles['backgroundColor'];
        delete skin.styles['backgroundColor'];
      }

      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.form = $('<div class="ve_inner" />').appendTo(a.dom);
      if (labelStr) {
        // 带标签控件
        a.form.addClass("ve_form");
        a.labelBox = $('<div class="labelBox" />').appendTo(a.form);
        a.label = $('<span class="ve_field" />').html(labelStr).appendTo($('<div class="label"><span class="ve_z"></span></div>').appendTo(a.labelBox));
        if (a.showDetail)
          a.labelBox.append('<div class="detail">&nbsp;</div>');

        if (lbStyle) {
          if (lbStyle.a) {
            if (lbStyle.a == 'justify') {
              skin.labelStyles['textAlignLast'] = lbStyle.a;
            } else {
              skin.labelStyles['textAlign'] = lbStyle.a;
            }
          }
          if (lbStyle.w)
            skin.labelStyles['width'] = lbStyle.w;
          if (lbStyle.c)
            skin.labelStyles['color'] = lbStyle.c;
        }
        a.label.css(skin.labelStyles)
      }
      a.inputBox = $('<div class="inputBox" />').appendTo(a.form);

      a.wrapper = $('<div class="ve_input ve_yearmonth" ' + attrs + '/>').appendTo(a.inputBox);
      a.input = $('<input type="text" style="width:4em" maxlength="4" />').appendTo(a.wrapper);
      $('<span> / </span>').appendTo(a.wrapper);
      a.input2 = $('<input type="text" style="width:3em" maxlength="2" />').appendTo(a.wrapper);

      a.input.css(skin.inputStyles);
      a.input2 && a.input2.css(skin.inputStyles);
      if (a.showDetail)
        a.inputBox.append('<div class="detail">&nbsp;</div>');

      var tmpYear = '', tmpMonth = '';
      a.input.off('input').on('input', function (e) {
        if (this.value == '') {
          tmpYear = '';
          return;
        }
        var x = parseInt(this.value.replace(/[^\d]/g));
        if (x > 2999)
          this.value = tmpYear;
        else
          this.value = x;
        tmpYear = this.value
      })
      a.input2.off('input').on('input', function (e) {
        if (this.value == '') {
          tmpMonth = '';
          return;
        }
        var x = parseInt(this.value.replace(/[^\d]/g));
        if (x > 12)
          this.value = tmpMonth;
        else
          this.value = x;
        tmpMonth = this.value
      })
      a.input.bind('change', function (e) {
        c();
      })
      a.input2.bind('change', function (e) {
        var x = this.value;
        if (x) {
          if (parseInt(x) < 10)
            this.value = '0' + x;
        }
        c();
      })

      var c = function () {
        var year = a.input.val(), month = a.input2.val();
        if (!year && !month)
          a.value = '';

        var date = $F.parseDate('1900-01-01 00:00:00');
        if (year)
          date.setYear(parseInt(year));
        if (month)
          date.setMonth(parseInt(month - 1));
        a.value = date.format('yyyy-MM');

        if (a.dataProp) {
          a.instance.fireValue(a.dataProp, a.runtime, a.evt(), a);
        }
      }

      makeValueAndState(a);
    }

    state(e) {
      if (e == undefined)
        return this.widgetState;
      this.widgetState = e;
      if (e == 1)
        this.dom.hide(); else {
        this.dom.show();
        this.disable(e == 2);
      }
      this.input.prop('readonly', (e == 3));
      this.input2.prop('readonly', (e == 3));
      if (this.form)
        this.form.toggleClass('readonly', e == 3);
    }

    val(e) {
      var a = this;
      if (!arguments.length) {
        return a.value;
      }

      if (!e) {
        a.input.val('');
        a.input2.val('');
        a.value = '';
        return;
      }
      e = $F.parseDate(e);
      if (e.getFullYear() == 1900) {
        if (a.dataProp)
          a.instance.fireValue(a.dataProp, a.runtime, {
            type: 0,
            data: ''
          }, a);
        a.input.val('');
        a.input2.val('');
        a.value = '';
        return;
      }
      a.value = e;

      var year = e.getYear() + 1900, month = e.getMonth();
      if (month < 10)
        month = '0' + month;
      a.input.val(year);
      a.input2.val(month);
    }

    verify() {
      const a = this;
      if (super.verify()) {
        if (a.widgetState == 1) return true;
        try {
          if (localPro(a, 'required') && !a.val()) {
            const tip = localPro(a, 'required', '', 1) + $F.lang('sys.invalid');
            if (a.showDetail)
              $('.detail', a.inputBox).text(tip)
            else {
              $F.notify(tip, -1);
            }
            a.form.addClass('ve_input_error')
            return false;
          } else {
            $('.detail', a.inputBox).html('&nbsp;')
            a.form.removeClass('ve_input_error')
          }
          return true;
        } catch (e) {
          console.error(e);
        }
      }
      return false;
    }

    bind(e, callback) {
      if (e == 'change')
        return;
      super.bind(e, callback);
    }
  }

  /*
	 * 动态页面组件：label
	 */
  class CtlLabel extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a),
        labelStr = localPro(a, 'label', '', true),
        lbStyle = localPro(a, 'lbStyle'),
        wrap = localPro(a, 'wrap');

      var height = skin.styles['height'];
      if (!height || height == 'auto') {
        height = '2.2em';
      }
      if (!wrap) {
        skin.labelStyles['lineHeight'] = height;
        skin.inputStyles['height'] = height;
      }

      if (wrap == 'wrap') {
        skin.inputStyles['whiteSpace'] = 'pre-wrap';
        skin.inputStyles['wordBreak'] = 'break-word';
      } else if (wrap == 'wrapAll') {
        skin.inputStyles['whiteSpace'] = 'normal';
        skin.inputStyles['wordBreak'] = 'break-all';
      } else {
        skin.inputStyles['whiteSpace'] = 'nowrap';
        skin.inputStyles['lineHeight'] = skin.inputStyles['lineHeight'] || height;
        skin.inputStyles['overflow'] = 'hidden';
        skin.inputStyles['textOverflow'] = 'ellipsis';

      }

      if (labelStr) {
        a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
        var fakeBox = $('<div class="wl" />').appendTo(a.dom);
        a.label = $('<span class="dyn_as dyn_label" />').html(labelStr).appendTo(fakeBox);
        if (lbStyle) {
          if (lbStyle.a) {
            if (lbStyle.a == 'justify') {
              skin.labelStyles['textAlignLast'] = lbStyle.a;
            } else {
              skin.labelStyles['textAlign'] = lbStyle.a;
            }
          }
          if (lbStyle.w)
            skin.labelStyles['width'] = lbStyle.w;
          if (lbStyle.c)
            skin.labelStyles['color'] = lbStyle.c;
        }
        a.label.css(skin.labelStyles);
        a.input = $('<span class="wlc" />').appendTo(fakeBox);
      } else {
        a.input = a.dom = $('<span data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
        //skin.inputStyles['overflow'] = 'auto';
      }
      if (localPro(a, 'align'))
        a.input.css('textAlign', localPro(a, 'align'));
      if (localPro(a, 'lh'))
        a.input.css('lineHeight', localPro(a, 'lh'));

      a.input.html(localPro(a, 'res', '', true) || '')
      a.input.css(skin.inputStyles);

      // a.input.on('mouseover', function (e) {
      //   a.makeTooltip()
      // });
      makeValueAndState(a);
    }

    makeTooltip() {
      const a = this;
      if (a.input[0].offsetWidth < a.input[0].scrollWidth) {
        a.input.attr('title', a.input.text())
      }
      if (localPro(a, 'tipper') == 'Tipped') {
        Tipped.remove(a.input);
        Tipped.create(a.input);
      }
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.value;

      a.value = e;
      if ($.type(e) == 'string') {
        e = $F.ognl(e.trim(), function (propertyName) {
          return a.instance.getValue(propertyName, a.runtime);
        })
      } else
        e = e + '';

      var format = localPro(a, 'format');
      format = $F.ognl(format, function (propertyName) {
        return a.instance.getValue(propertyName, a.runtime);
      })
      if (!format) {
        if (e.toUpperCase().startWith('<HTML>'))
          e = e.substring(6);
        else
          e = e.replaceAll('\n', '<br/>');
        a.input.html(Helper.filterXSS(e));
      } else {
        a.input.html(CtlUtils.expression(e, format, a.runtime))
      }
    }
  }

  /*
	 * 动态页面组件：script
	 */
  class CtlScript extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a);
      a.input = a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (localPro(a, 'align'))
        a.input.css('textAlign', localPro(a, 'align'));

      a.val(localPro(a, 'res') || '')
      a.input.css(skin.inputStyles);
      a.fragment = localPro(a, 'fragment');

      makeValueAndState(a);
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.value;

      a.value = e;
      if (a.fragment) {
        if (e) {
          var busyId = $E.progressing || $E.silent ? '' : $F.busyShow('Fragment loading...');
          Ajax.post({
            url: $E.url + '/fs/fragments/read_',
            data: {
              path: e,
            },
            success: function (data) {
              if (data.code > 0) {
                a.input.html(Helper.filterXSS(data.value));
                if ($.isFunction(window.prettyPrint))
                  window.prettyPrint(null, a.input[0]);
              } else {
                a.input.html('<div class="red">' + data.message + '</div>')
                $F.err('Ctls - Script', data.message);
              }
            },
            always: function () {
              $F.busyHide(busyId)
            }
          })
        } else
          a.input.html('');
      } else {
        if ($.type(e) == 'string') {
          e = $F.ognl(e.trim(), function (propertyName) {
            return a.instance.getValue(propertyName, a.runtime);
          });
          a.input.html(Helper.filterXSS(e));

          setTimeout(() => {
            var imgs = a.input.find($('img'));
            if (imgs.length > 0) {
              imgs.each(function () {
                $(this).bind('dblclick', function (e) {
                  e.preventDefault();
                  e.stopPropagation();
                  $F.imageView(e.target.src)
                })
              })
            }
          }, 0);
        } else
          a.input.html('');
      }
    }
  }

  /*
	 * 动态页面组件：marquee
	 */
  class CtlMarquee extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a);
      var height = skin.styles['height'];
      if (!height || height == 'auto')
        skin.styles['height'] = '2.2em';
      delete skin.styles['overflow'];
      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.input = a.dom.addClass('dyn_marquee');
      a.marquee = $('<div class="flyerbox"/>').appendTo(a.dom).marquee();

      makeValueAndState(a);
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.value;

      a.value = e;
      a.marquee.clear();
      //console.log(a.flyer)
      if (e instanceof Array) {
        if (e.length == 0) {
          // for test
          e.push('elite.ngs marquee board');
        }
        // console.log(a.flyer);
        $.each(e, function (i, v) {
          a.marquee.addItem($('<span class="item" />').text(v));
        })
        a.marquee.flyer.find('span.item').css({
          'display': 'inline-block',
          'lineHeight': '2.2em'
        })
      } else
        $F.err('Ctls - Label', '跑马灯控件只接受数组变量')
    }

    add(e) {
      var a = this,
        item = $('<span class="item" style="display:inline-block;line-height:2.2em" />').text(e);
      a.marquee.addItem(item);
      a.value = a.value || [];
      a.value.push(e);
      if (a.dataProp) {
        a.instance.fireValue(a.dataProp, a.runtime, {
          type: 0,
          data: a.value
        }, a)
      }
    }
  }

  /*
	 * 动态页面组件：button
	 */
  class CtlButton extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      a.async = localPro(a, 'async', 1);
      //console.log(a);
      var skin = makeStyles(a),
        labelStr = localPro(a, 'label', '', true),
        iconObj = localPro(a, 'icon');

      a.dom = a.input = $('<button data-type="' + a.type + '" data-path="' + a.path + '" type="button" />')
        .appendTo(container)
        //.addClass('ngs')
        .addClass(skin.clazzs.join(' '))
        .css(skin.styles);
      a.label = $('<span class="js_label"></span>').html(labelStr || '');
      if (iconObj) {
        // 包含图标
        a.icon = CtlUtils.parseIcon(iconObj);
        if (iconObj.d == 1 || iconObj.d == 3) {
          a.input.append(a.label);
          a.input.append(a.icon)
          if (iconObj.d == 1)
            a.icon.addClass('pull-right');
        } else {
          a.input.append(a.icon)
          a.input.append(a.label);
          if (!iconObj.d)
            a.icon.addClass('pull-left');
        }
        if (iconObj.s)
          a.icon.css('fontSize', iconObj.s + 'em');
        if (iconObj.d > 1) {
          a.label.css('display', 'block')
        }
        a.dom.addClass('dyn_valign')
      } else {
        a.input.append(a.label);
      }

      makeValueAndState(a);
    }

    bind(e, callback) {
      var a = this;
      if (e == 'click') {
        a.input.on('click', function (e) {
          if (a.input.data('_eventClick')) {
            //console.log('Ignored replicated click event');
            return;
          }

          a.input.data('_eventClick', true);
          callback(e);
          setTimeout(function () {
            if (a.input)
              a.input.removeData('_eventClick');
          }, 10)
          a.trace({
            type: 'click'
          })
        })
      } else
        a.input.bind(e, callback)
    }
  }

  /*
	 * 动态页面组件：radio
	 */
  class CtlRadio extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a),
        labelStr = localPro(a, 'label', '', true),
        inputName = localPro(a, 'inputName', ''),
        dataSource = localPro(a, 'dataSource');

      a.inputName = a.instance.id + '_' +
        a.namespace.replaceAll('\\.', '_') +
        '_radio_' +
        ((inputName || a.dataProp || '').replaceAll('\\.', '_') || 'NA');
      if (dataSource || this.dsSerial) {
        // 使用数据源
        delete skin.styles['height'];
        a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />')
          .appendTo(container)
          .addClass(skin.clazzs.join(' '))
          .css(skin.styles);
        a.dom.css({
          display: 'block',
          height: 'auto',
          width: 'auto'
        })
        a.instance.bindValue(dataSource, a, function (e) {
          if (e.type) return;
          a.setOptions(e.data);
        }, true);
        a.input = a.dom;
      } else {
        var height = skin.styles['height'];
        if (!height || height == 'auto') {
          //skin.styles['line-height'] = '2.2em';
          delete skin.styles['height'];
        } else
          skin.styles['line-height'] = height;
        a.dom = $('<label data-type="' + a.type + '" data-path="' + a.path + '" />')
          .appendTo(container)
          .addClass(skin.clazzs.join(' '))
          .css(skin.styles);
        a.input = $('<input type="radio" name="' + a.inputName + '" data-path="' + a.path + '"  value="' + localPro(a, 'option', '') + '" />').appendTo(a.dom);
        a.dom.append($('<span />').html(labelStr));
        a.input.on('click', function (e) {
          if (a.dataProp)
            a.instance.fireValue(a.dataProp, runtime, a.evt(), a);
        });
      }

      makeValueAndState(a);
    }

    setOptions(data) {
      var a = this;
      a.dom.empty();
      $.each(data, function (i, v) {
        a.form = $('<label style="line-height: 2.2em" />').appendTo(a.dom);
        a.input = $('<input type="radio" name="' + a.inputName + '" value="' + v[0] + '" />').appendTo(a.form);
        a.form.append($('<span />').text(v[1]));
        a.input.on('click', function (e) {
          if (a.dataProp)
            a.instance.fireValue(a.dataProp, a.runtime, a.evt(), a);
        });

        if (i + 1 < data.length)
          a.form.css('margin-right', '10px')
        a.disable(getProp(a, 'styles', 'state', 0))
      })
    }

    val(e) {
      var a = this;
      if (e === undefined) {
        var checked = $('input[type=radio][name="' + a.inputName + '"]:checked', a.container);
        if (checked.length > 0)
          return a.instance.getValueByPropertyName(a.runtime, a.dataProp, checked[0].value || 1);
        else
          return a.instance.getValueByPropertyName(a.runtime, a.dataProp, '');
      } else {
        $('input[type=radio][name="' + a.inputName + '"]', a.container).each(function (i, v) {
          v.checked = (v.value ? (v.value == e.toString()) : e)
        })
      }
    }
  }

  /*
	 * 动态页面组件：checkbox
	 */
  class CtlCheckbox extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a),
        labelStr = localPro(a, 'label', '', true);
      a.dataSource = localPro(a, 'dataSource');

      a.inputName = a.instance.id + '_' + a.runtime.namespace.replaceAll('\\.', '_') + '_ckb_' + (a.dataProp ? a.dataProp.replaceAll('\\.', '_') : 'NA');
      if (a.dataSource || this.dsSerial) {
        // 使用数据源
        delete skin.styles['height'];
        a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
        a.dom.css({
          display: 'block',
          height: 'auto',
          width: 'auto'
        })
        a.instance.bindValue(a.dataSource, a, function (e) {
          if (e.type)
            return;
          a.setOptions(e.data);
        }, true);
      } else {
        var height = skin.styles['height'];
        if (!height || height == 'auto') {
          //skin.styles['line-height'] = '2.2em';
          delete skin.styles['height'];
        } else
          skin.styles['line-height'] = height;

        a.option = localPro(a, 'option');
        //a.option = Verify.isDigit(option) ? parseFloat(option) : option;
        a.dom = $('<label data-type="' + a.type + '" data-path="' + a.path + '" />')
          .appendTo(container)
          .addClass(skin.clazzs.join(' '))
          .css(skin.styles);
        a.input = $('<input type="checkbox" name="' + a.inputName + '" data-path="' + a.path + '" value="' + a.option + '" />').appendTo(a.dom);
        a.dom.append($('<span />').html(labelStr))
        a.input.on('click', function (e) {
          if (a.input.prop('checked')) {
            // 处理互斥
            $('input[type=checkbox][name="' + a.inputName + '"]:checked', container).each(function () {
              var ckb = $(this);
              if (ckb.attr('data-path') !== a.path)
                ckb[0].checked = false;
            })
          }
          if (a.dataProp)
            a.instance.fireValue(a.dataProp, a.runtime, a.evt(), a);

          if (H5Utils.isFirefox)
            a.input.trigger('change')
        });
      }

      makeValueAndState(a);
    }

    setOptions(data) {
      var a = this;
      a.dom.empty();
      $.each(data, function (i, v) {
        a.form = $('<label style="line-height: 2.2em" />').appendTo(a.dom);
        a.input = $('<input type="checkbox" name="' + a.inputName + '" value="' + v[0] + '" />').appendTo(a.form);
        a.form.append($('<span />').text(v[1]))
        a.input.on('click', function (e) {
          var multi = localPro(a, 'multi')
          if (a.input.prop('checked')) {
            // 处理多选数量
            var checked = $('input[type=checkbox][name="' + a.inputName + '"]:checked', a.container);
            if (multi && checked.length > multi)
              a.input.prop('checked', false);
            else if (a.dataProp)
              a.instance.fireValue(a.dataProp, a.runtime, a.evt(), a);
          } else {
            if (a.dataProp)
              a.instance.fireValue(a.dataProp, a.runtime, a.evt(), a);
          }
        });
        if (i + 1 < data.length)
          a.form.css('margin-right', '10px')
        a.disable(getProp(a, 'styles', 'state', 0))
      })
    }

    val(e) {
      const a = this;
      if (a.dataSource || a.dsSerial) {
        if (!arguments.length) {
          let cs = [];
          $('input[type=checkbox][name="' + a.inputName + '"]:checked', a.container).each(function () {
            cs.push(this.value)
          })
          if (a.serial) {
            cs = cs.join(',')
          }
          return cs;
        } else {
          $('input[type=checkbox][name="' + a.inputName + '"]', a.container).prop('checked', false);
          e.split(',').forEach(function (v) {
            $('input[type=checkbox][name="' + a.inputName + '"][value="' + v + '"]', a.container).prop('checked', true);
          })
        }
      } else {
        if (!arguments.length) {
          if (a.input.prop('checked'))
            return a.option;
          else
            return a.dataProp ? a.instance.getValueByPropertyName(a.runtime, a.dataProp, '') : '';
        } else {
          a.input.prop('checked', e == a.option);
        }
      }
    }

    bind(e, callback) {
      const a = this;
      if (a.dataSource) {

      } else
        super.bind(e, callback);
    }
  }

  /*
	 * 动态页面组件：switcher
	 */
  class CtlSwitcher extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a),
        onTip = localPro(a, 'onTip', '', true),
        onBC = localPro(a, 'onBC', '#6eae8c'),
        offTip = localPro(a, 'offTip', '', true),
        offBC = localPro(a, 'offBC');

      if (!skin.styles['width'])
        skin.styles['width'] = '4em';

      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" tabIndex="-1" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.input = a.dom.wisSwitch({
        bgColor: {
          on: onBC,
          off: offBC
        },
        offTip: offTip,
        onTip: onTip,
        onChanged: function (e, state) {
          a.changeCallback.forEach(function (callback, i) {
            callback(e, state);
          })
        }
      })
      a.changeCallback = [];
      makeValueAndState(a);
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.input.val();

      a.value = e;
      a.input.val(e);
    }

    bind(e, callback) {
      if (e == 'change') {
        this.changeCallback.push(callback);
      }
    }
  }

  /*
	 * 动态页面组件：combox
	 */
  class CtlCombox extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      const a = this;
      // console.log(a);
      let skin = makeStyles(a),
        placeholder = localPro(a, 'placeholder', '', true),
        labelStr = localPro(a, 'label', '', true),
        lbStyle = localPro(a, 'lbStyle');

      a.showDetail = localPro(a, 'detail')
      a.multi = localPro(a, 'multi');
      a.multiNum = localPro(a, 'multiNum', 0, true);
      a.inputable = localPro(a, 'inputable');
      a.labelProp = localPro(a, 'labelProp');
      a.changeCalls = []; // 选择改变时的回调方法队列
      a.comboxWidth = localPro(a, 'comboxWidth', 0)

      if (skin.styles['color']) {
        skin.inputStyles['color'] = skin.styles['color'];
        delete skin.styles['color'];
      }
      if (skin.styles['backgroundColor']) {
        skin.inputStyles['backgroundColor'] = skin.styles['backgroundColor'];
        delete skin.styles['backgroundColor'];
      }

      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (labelStr) {
        // 带标签控件
        a.form = $('<div class="ve_form" />').appendTo(a.dom);
        a.labelBox = $('<div class="labelBox" />').appendTo(a.form);
        a.label = $('<span class="ve_field" />').html(labelStr).appendTo($('<div class="label"><span class="ve_z"></span></div>').appendTo(a.labelBox));
        if (a.showDetail)
          a.labelBox.append('<div class="detail">&nbsp;</div>');
        if (lbStyle) {
          if (lbStyle.a) {
            if (lbStyle.a == 'justify') {
              skin.labelStyles['textAlignLast'] = lbStyle.a;
            } else {
              skin.labelStyles['textAlign'] = lbStyle.a;
            }
          }
          if (lbStyle.w)
            skin.labelStyles['width'] = lbStyle.w;
          if (lbStyle.c)
            skin.labelStyles['color'] = lbStyle.c;
        }
        a.label.css(skin.labelStyles);
      } else {
        a.form = a.dom;
      }
      a.inputBox = $('<div class="inputBox" />').appendTo(a.form);
      if (a.multi || a.inputable) {
        a.input = $('<div class="ve_input" />').appendTo(a.inputBox);
      } else {
        a.input = $('<select class="ve_input" />').appendTo(a.inputBox);
      }
      a.input.css(skin.inputStyles);
      if (a.showDetail)
        a.inputBox.append('<div class="detail">&nbsp;</div>');

      if (localPro(a, 'required'))
        a.input.attr('required', true);

      if (a.multi || a.inputable) {
        a.input.on('click', function () {
          a.verify()
        })
      } else {
        a.input.on('change blur', function () {
          a.verify()
        })
      }

      if (a.multiNum) {
        a.instance.bindValue(a.multiNum, a, function (e) {
          a.multiNum = e.data;
        }, true);
      }

      if (a.multi || a.inputable) {
        a.input = a.input.wisCombox({
          lang: $E.lang,
          multi: a.multi,
          tip: placeholder,
          inputable: a.inputable,
          fixedWidth: false,
          comboxWidth: a.comboxWidth,
          onSelected: function (data) {
            if (a.changeCalls) {
              // console.log('oldData/newData:', a.input._oldData, data)
              if (a.input._oldData == data || data == undefined)
                return;

              if (a.multiNum > 0 && data.length > a.multiNum) {
                $F.notify('已超过可选上限，最多可选个数为：' + a.multiNum, -1);
                let str = [];
                a.input._oldData.forEach(item => {
                  str.push(item.value);
                })
                a.input.val(str);
                return;
              }
              a.input._oldData = data;
              $.each(a.changeCalls, function () {
                this({}, data)
              })
            }
            a.verify()
          }
        })
        if (skin.inputStyles.backgroundColor)
          a.input.input.css('backgroundColor', skin.inputStyles.backgroundColor);
        if (skin.inputStyles.color)
          a.input.input.css('color', skin.inputStyles.color);
      } else {
        a.input.on('keydown', function (e) {
          if (e.keyCode == 46 || e.keyCode == 8) {
            a.input.val('').trigger('change');
          }
        }).data('ctrl', a);
        a.input.on('change', function () {
          let v = a.input.val()
          a.input.toggleClass('invalid', v == '' || v == null || v == undefined)
        })
      }

      let defopt = localPro(a, 'defOpt');
      if (defopt) {
        a.defaultOption = {
          label: defopt.l == '!' ? '' : defopt.l,
          value: defopt.v || ''
        }
      }
      a.defaultValue = '';
      a.input.empty();
      let dataSource = localPro(a, 'dataSource', {});
      if (!dataSource.type) {
        // 处理预加载数据选项
        let cs = [];
        if (dataSource.options) {
          for (let key in dataSource.options) {
            cs.push({
              label: i18nResource(a, dataSource.options[key]),
              value: key
            })
          }
        }
        a.setOptions(cs);
        if (!a.dataProp)
          a.val('');
      } else if (dataSource.type == 1) {
        // 处理动态加载数据选项
        if (dataSource.options && dataSource.options.sql) {
          let vf = dataSource.options.key,
            lf = dataSource.options.value,
            cachekey = dataSource.options.cache,
            sql = dataSource.options.sql,
            binds = dataSource.options.binds || [];
          if (cachekey)
            cachekey = runtime.struct.id + '.' + cachekey;
          if (cachekey && a.project.cache[cachekey]) {
            a.setOptions(getDataFromSql(a.project.cache[cachekey], vf, lf));
          } else {
            a.waitForOptions = true;
            // 变量传参遍历
            let params = {};
            $.each(binds, function (i, v) {
              if (v.startWith('@')) {
                let x = parseInt(v.substring(1));
                params['C' + (i + 1)] = a.xData[x - 1];
                a.xitemListener = a.xitemListener || {};
                a.xitemListener[x] = function (e) {
                  params['C' + (i + 1)] = e;
                  exeSql(a, cachekey, sql, params, vf, lf, a.input.val());
                }
              } else {
                params['C' + (i + 1)] = a.instance.getValue(v, runtime);
                a.instance.bindValue(v, a, function (e) {
                  params['C' + (i + 1)] = e.data;
                  exeSql(a, cachekey, sql, params, vf, lf, a.dataProp ? a.instance.getValue(a.dataProp, runtime) : a.input.val());
                });
              }
            });
            exeSql(a, cachekey, sql, params, vf, lf, (a.dataProp ? a.instance.getValue(a.dataProp, runtime) : undefined));
          }
        }
      } else if (dataSource.type == 2) {
        // 处理全局SQL数据项
        let vf = dataSource.options.key,
          lf = dataSource.options.value,
          cachekey = dataSource.options.cache,
          sql = dataSource.options.sql,
          params = [];
        //console.error('JSONSQL:' + sql);
        $F.ognl(sql, function (propertyName) {
          params.push(propertyName);
        })
        let ognlOptions = function (value) {
          let jsonSql = $F.ognl(sql, function (propertyName) {
            if (propertyName.startWith('@')) {
              let x = parseInt(propertyName.substring(1));
              return a.xData[x - 1];
            } else {
              return getValue(a.instance, propertyName, a.runtime);
            }
          })
          let data = a.project.queryCodetable(cachekey, jsonSql);
          a.setOptions(getDataFromJsonSql(data, vf, lf), value)
        }
        if (params.length > 0) {
          // 使用变量入参
          $.each(params, function (i, v) {
            if (v.startWith('@')) {
              a.xitemListener = a.xitemListener || {};
              a.xitemListener[parseInt(v.substring(1))] = function (e) {
                ognlOptions(a.input.val())
              }
            } else {
              a.instance.bindValue(v, a, function (e) {
                ognlOptions(a.dataProp ? a.instance.getValue(a.dataProp, runtime) : a.input.val())
              })
            }
          })
        }
        ognlOptions(a.dataProp ? a.instance.getValue(a.dataProp, runtime) : undefined);
      } else if (dataSource.type == 3) {
        // 处理绑定变量的数据源
        let options = dataSource.options;
        //console.log(options)
        if (options && options.n) {
          a.instance.bindValue(options.n, a, function (e) {
            if (e.type)
              return;
            if (!options.t)
              a.setOptions(getDataFromProperty(e.data), (a.dataProp ? a.instance.getValue(a.dataProp, runtime) : a.input.val()));
            else
              a.setOptions(getDataFromProperty(e.data, options.v, options.l), (a.dataProp ? a.instance.getValue(a.dataProp, runtime) : a.input.val()));
          })
          let data = a.instance.getValue(dataSource.options.n, runtime);
          if (data) {
            if (!options.t)
              a.setOptions(getDataFromProperty(data), (a.dataProp ? a.instance.getValue(a.dataProp, runtime) : undefined));
            else
              a.setOptions(getDataFromProperty(data, options.v, options.l), (a.dataProp ? a.instance.getValue(a.dataProp, runtime) : undefined));

          }
        }
      }

      makeValueAndState(a);
    }

    setOptions(data, value) {
      const a = this;
      a.waitForOptions = false;
      if ($.isArray(data)) {
        data = data.map((item) => {
          if ($.isArray(item)) {
            return {
              value: String(item[0]),
              label: item[1] || ''
            }
          }
          return item
        })
      }
      if (a.multi || a.inputable) {
        let cs = [];
        if (a.defaultOption) {
          cs.push({
            label: i18nResource(a, a.defaultOption.label),
            value: a.defaultOption.value
          })
        }
        a.input.setData(cs.concat(data))
      } else {
        a.input.empty();
        let placeholder = localPro(a, 'placeholder', '', 1);
        if (placeholder)
          a.input.append('<option value="" disabled>' + placeholder + '</option>');
        if (a.defaultOption)
          a.input.append($('<option />').attr('value', a.defaultOption.value).text(i18nResource(a, a.defaultOption.label)));
        // console.log(data)
        $.each(data, function (i, v) {
          a.input.append($('<option />').attr('value', v.value).text(i18nResource(a, v.label)));
        })
      }

      let newValue = (value == undefined) ? a.defaultValue : value;
      if (newValue !== a.val()) {
        a.val(newValue);
      }
      if (value == undefined && a.defaultValue != '') {
        // 必须是脚本刷新选项情况下
        console.log('[COMBOX][' + a.id + '] - defaultValue:' + a.defaultValue)
        a.input.trigger('change');
      }
    }

    bind(e, callback) {
      const a = this;
      if (e == 'change' && (a.multi || a.inputable))
        a.changeCalls.push(callback);
      else
        a.input.bind(e, callback);
    }

    /**
     * @param unbind {boolean}
     *  fixed by: kevin 2018/08/19
     *  当变量改变消息源来自不可逆操作源（比如客户资料属性变动），不需要做回归校验
     */
    val(e, unbind) {
      const a = this;
      if (e === undefined) {
        if (a.multi || a.inputable) {
          // console.log('WisdgeCombox getSelected')
          if (a.multi) {
            let l = [], v = [];
            $.each(a.input.val(), function () {
              l.push(this.label);
              v.push(this.value);
            })
            if (a.labelProp) {
              a.instance.fireValue(a.labelProp, a.runtime, {
                type: 0,
                data: l.join(',')
              }, a)
            }
            return v.join(',');
          } else {
            let v = a.input.val();
            if (a.labelProp) {
              a.instance.fireValue(a.labelProp, a.runtime, {
                type: 0,
                data: (v ? v.label : '')
              }, a)
            }
            return v ? v.value : '';
          }
        } else {
          if (a.labelProp) {
            a.instance.fireValue(a.labelProp, a.runtime, {
              type: 0,
              data: a.input.find("option:selected").text()
            }, a)
          }
          if (a.dataProp)
            return a.instance.getValueByPropertyName(a.runtime, a.dataProp, a.input.val());
          else
            return a.input.val();
        }
      } else {
        if (!a.waitForOptions) {
          if ($.type(e) === 'string')
            e = e.trim();

          //console.log('[COMBOX][' + a.id + ' - setValue:' + t)
          a.defaultValue = e;
          a.input._oldData = e;
          if (a.multi || a.inputable) {
            if (a.multi) {
              let s = e;
              if (typeof s === 'string'){
                s = s.split(',');
              }
              a.input.val(s);
              if (a.labelProp) {
                let l = [];
                $.each(a.input.val(), function () {
                  l.push(this.label);
                })
                a.instance.fireValue(a.labelProp, a.runtime, {
                  type: 0,
                  data: l.join(',')
                }, a)
              }
            } else {
              a.input.val(e);
              let b = a.input.val();
              if (!unbind) {
                if (!b || !$F.equalValue(b.value, e)) {
                  if ($E.debug && (e === '' || e === null || e === undefined))
                    $F.log('CtlCombox', '[' + a.path + ']选项匹配值失败', e);
                  a.input.val('');
                  if (a.dataProp)
                    a.instance.fireValue(a.dataProp, a.runtime, {
                      type: 0,
                      data: ''
                    }, a);
                }
              }
              if (a.labelProp && a.input.val()) {
                a.instance.fireValue(a.labelProp, a.runtime, {
                  type: 0,
                  data: b ? b.label : ''
                })
              }
            }
          } else {
            a.input.val(e);
            if (!unbind) {
              if (e !== '' && a.input.val() == null) {
                // 赋值不成功，当前选项无匹配
                let cs = [];
                $('option', a.input).each(function () {
                  cs.push(this.value)
                })
                if ($E.debug && (e === '' || e === null || e === undefined))
                  $F.log('CtlCombox', '[' + a.path + ']选项匹配值失败', e);
                a.input.val('');
                if (a.dataProp)
                  a.instance.fireValue(a.dataProp, a.runtime, {
                    type: 0,
                    data: ''
                  }, a);
              }
            }
            let v = a.input.val();
            a.input.toggleClass('invalid', v == '' || v == null || v == undefined)

            if (a.labelProp) {
              a.instance.fireValue(a.labelProp, a.runtime, {
                type: 0,
                data: a.input.find("option:selected").text()
              }, a)
            }
          }
        } else
          a.defaultValue = e;
      }
    }

    verify() {
      const a = this;
      if (super.verify()) {
        if (a.widgetState == 1) return true;
        try {
          if (localPro(a, 'required') && !a.val()) {
            const tip = localPro(a, 'required', '', 1) + $F.lang('sys.invalid');
            if (a.showDetail)
              $('.detail', a.inputBox).text(tip)
            else {
              $F.notify(tip, -1);
            }
            a.form.addClass('ve_input_error')
            return false;
          } else {
            $('.detail', a.inputBox).html('&nbsp;')
            a.form.removeClass('ve_input_error')
          }
          return true;
        } catch (e) {
          console.error(e);
        }
      }
      return false;
    }

    reset() {
      const a = this;
      if (a.dataProp) {
        let v = a.instance.getValue(a.dataProp, a.runtime);
        a.val(v);
//				if (a.multi || a.inputable) {
//					$.each(a.changeCalls, function() {
//						this({}, v)
//					})
//				} else
//					a.input.trigger('change');
        //console.info('Combox reset to : ' + v);
      }
      $('.detail', a.inputBox).html('&nbsp;')
      a.form.removeClass('ve_input_error')
    }

    checkAll() {
      if (a.multi)
        this.input.checkAll();
      else
        $F.err('Ctls - Combox', '非多选下拉控件，不可进行全选')
    }
  }

  /*
	 * 动态页面组件：line
	 */
  class CtlLine extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      const a = this;
      //console.log(a);
      var skin = makeStyles(a);
      if (!skin.styles['margin'])
        skin.styles['margin'] = 0;
      a.dom = a.input = $('<hr data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);

      makeValueAndState(a);
    }
  }


  /*
	 * 动态页面组件：<i/>
	 */
  class CtlTag_i extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a);
      a.dom = a.input = $('<i data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);

      makeValueAndState(a);
    }
  }


  /*
	 * 动态页面组件：link
	 */
  class CtlLink extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      a.async = localPro(a, 'async', 1);
      // console.log(a);
      var skin = makeStyles(a),
        labelStr = localPro(a, 'label', '', true),
        labelProp = localPro(a, 'labelProp'),
        iconObj = localPro(a, 'icon');

      skin.styles['display'] = skin.styles['display'] || 'inline-block';
      a.dom = a.input = $('<a data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.label = $('<span style="cursor:inherit" />')
      if (labelProp) {
        // 使用标签变量
        a.label.text(a.instance.getValue(labelProp, runtime));
        a.instance.bindValue(labelProp, a, function (e) {
          a.label.html(Helper.filterXSS(e.data));
        })
      } else
        a.label.html(Helper.filterXSS(labelStr));

      if (iconObj) {
        // 包含图标
        var icon = CtlUtils.parseIcon(iconObj);
        if (iconObj.d == 1 || iconObj.d == 3) {
          a.input.append(a.label);
          a.input.append(icon)
          if (iconObj.d == 1)
            icon.addClass('pull-right');
        } else {
          a.input.append(icon)
          a.input.append(a.label);
          if (!iconObj.d)
            icon.addClass('pull-left');
        }
        a.dom.addClass('dyn_valign')
      } else {
        a.input.append(a.label);
      }

      if (localPro(a, 'sp')) {
        a.input.on('click', function (e) {
          e.preventDefault();
          e.stopPropagation();
        })
      }
      var href = localPro(a, 'href');
      if (!a.dataProp && href) {
        if (href.t) {
          a.input.attr('href', 'javascript:;').on('click', function (e) {
            e.preventDefault();
            if (a.input.hasClass('disabled'))
              return;
            if (href.s) {
              doStructCmd(a, href.s);
              a.input.removeData('_clickEvent');
            }
          })
        } else {
          a.input.attr('href', href.s || 'javascript:;').attr('target', '_blank')
        }
      }

      makeValueAndState(a);
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.value;

      a.value = e;
      //a.input.find('>span').text(e);
      a.input.attr('href', e || 'javascript:;').attr('target', '_blank');
    }

    bind(e, callback) {
      var a = this;
      if (e == 'click') {
        a.input.bind('click', function (e) {
          e.preventDefault();
          if (a.input.hasClass('disabled'))
            return;
          if (a.input.data('_eventClick')) {
            // console.log('Ignored replicated click event');
            return;
          }

          a.input.data('_eventClick', true);
          callback(e);
          setTimeout(function () {
            if (a.input)
              a.input.removeData('_eventClick');
          }, 10)
          a.trace({
            type: 'click'
          })
        })
      } else {
        a.input.bind(e, callback);
      }
    }
  }

  /*
	 * 动态页面组件：image
	 */
  class CtlImage extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      // console.log(a);
      var skin = makeStyles(a),
        border = localPro(a, 'border'),
        stretch = localPro(a, 'stretch', 0),
        viewer = localPro(a, 'viewer'),
        resource = localPro(a, 'resource');

      if (!skin.styles['text-align'])
        skin.styles['text-align'] = 'center';
      if (!skin.styles['vertical-align'])
        skin.styles['vertical-align'] = 'middle';
      if (!skin.styles['overflow'])
        skin.styles['overflow'] = 'hidden';
      if (border)
        skin.styles['border'] = border;

      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.input = $('<img src="" />').appendTo(a.dom).css(skin.inputStyles);

      if (viewer) {
        a.input.addClass('jquery_viewer');
        a.input.css('cursor', 'pointer')
      }

      if (stretch == 0) {
        a.input.css({
          width: '',
          height: '',
          maxWidth: '100%',
          maxHeight: '100%'
        });
      } else if (stretch == 1) {
        a.input.css({
          width: '100%',
          height: '100%',
          minWidth: '',
          minHeight: ''
        });
      } else {
        a.input.css({
          width: '',
          height: '',
          minWidth: '',
          minHeight: ''
        });
      }

      if (resource) {
        // 使用本地文件资源
        a.val(resource);
      }

      makeValueAndState(a);
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.value;

      a.value = e;
      a.dom.removeClass('image')
      var x = e.toLowerCase(), src;
      if (x.startWith('http')) {
        src = e;
      } else if (x.startWith('res:')) {
        src = $E.url + '/' + e.substring(4);
      } else if (x.startWith('data:')) {
        src = e;
      } else if (x.startWith('/fs/')) {
        src = $E.url + e;
      } else if (x.startWith('fs/')) {
        src = $E.url + '/' + e;
      } else {
        if (e !== '') {
          src = $E.url + '/fs/get?file=' + e;
        } else {
          a.input[0].src = $E.url + '/images/i.gif';
          a.dom.css({
            lineHeight: '1em',
            fontSize: Math.min(a.dom.innerWidth(), a.dom.innerHeight()) + 'px'
          })
          a.dom.addClass('image');
        }
      }
      if (src) {
        if (localPro(a, 'async'))
          H5Utils.loadImage(a.input, src);
        else
          a.input[0].src = src;
      }
    }
  }

  /*
	 * 动态页面组件：timeline
	 */
  class CtlTimeline extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a);
      a.direct = localPro(a, 'direct');
      a.twoside = localPro(a, 'twoside');
      a.icon = localPro(a, 'icon');
      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.ul = $('<ul class="timeline"/>').appendTo(a.dom);
      if (a.direct === 1)
        a.ul.addClass('reverse');
      if (a.direct === 2)
        a.ul.addClass('row');
      if (a.twoside)
        a.ul.addClass('twoside');

      makeValueAndState(a);
    }

    val(e) {
      var a = this;
      if (!arguments.length)
        return a.value;

      a.value = e;
      a.ul.empty().html('');
      if ($.type(e) !== 'array') {
        $F.err('不合法的数组对象使用在时间轴上');
        return;
      }

      if (a.direct === 1)
        e = e.reverse();

      var li,
        direct,
        timeline,
        date,
        time,
        title,
        content,
        icon;
      $.each(e, function (i, v) {
        timeline = $F.parseDate(v[0]);
        date = timeline.format('yyyy/MM/dd');
        time = timeline.format('HH:mm');
        title = Helper.filterXSS(v[1]);
        content = Helper.filterXSS(v[2]).replace('\n', '<br/>');
        icon = a.icon ? v[3] : 'tree';
        if (i % 2 && a.twoside) {
          li = $('<li>'
            + '<span class="content"><h2 class="title">' + title + '</h2><p class="js_content">' + content + '</p></span>'
            + '<span class="icon"><i class="vl ' + $F.fas(icon) + '" ></i></span></span>'
            + '<span class="times"><span class="vl"><span class="date">' + date + '</span><span class="time">' + time + '</span></span></span>'
            + '</li>').data('bean', v).appendTo(a.ul);
        } else {
          li = $('<li>'
            + '<span class="times"><span class="vl"><span class="date">' + date + '</span><span class="time">' + time + '</span></span></span>'
            + '<span class="icon"><i class="vl ' + $F.fas(icon) + '"></i></span></span>'
            + '<span class="content"><h2 class="title">' + title + '</h2><p class="js_content">' + content + '</p></span>'
            + '</li>').data('bean', v).appendTo(a.ul);
        }
        if (a.callback) {
          li.css('cursor', 'pointer');
          $('.js_content', li).on('click', function (e) {
            var this$ = $(e.currentTarget);
            a.callback(e, {
              DATA: this$.data('bean')
            })
          }).data('bean', v)
        }
      })
    }

    bind(e, callback) {
      if (e == 'click')
        this.callback = callback;
      else
        this.dom.bind(e, callback);
    }
  }

  /*
	 * 动态页面组件：questions
	 */
  class CtlQuestions extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      a.sourceProp = localPro(a, 'sourceProp');
      //console.log(a);
      var skin = makeStyles(a);
      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.input = a.dom.wisQuestions({
        onChanged: function (question, row, value) {
          if (a.dataProp) {
            a.instance.fireValue(a.dataProp, a.runtime, {
              type: 2,
              key: question.key,
              data: value
            }, a)
          }
        }
      });

      if (a.sourceProp) {
        var data = a.instance.getValue(a.sourceProp, runtime);
        a.input.setData(data);
      }
      makeValueAndState(a);
    }

    val(e) {
      var a = this;
      if (!arguments.length)
        return a.value;

      a.value = e;
      a.input.val(e);
      return a;
    }

    bind(e, callback) {

    }
  }

  /*
	 * 动态页面组件：audio
	 */
  class CtlAudio extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      // console.log(a);
      var skin = makeStyles(a);
      skin.styles['height'] = '40px';
      a.rateProp = localPro(a, 'rateProp');
      a.dom = a.input = $('<audio />').appendTo(container);
      a.audio = a.dom.audio({
        skin: localPro(a, 'skin') || '',
        source: '',
        quickProp: localPro(a, 'quickProp', 10),
        autoplay: localPro(a, 'autoplay'),
        onPlay: function (src) {
          // console.log('Playing: ', src)
        },
        onRatechange: function (e) {
          // console.log('Rate: ', e);
          if (a.rateProp) {
            a.instance.fireValue(a.rateProp, a.runtime, {
              type: 0,
              data: e
            }, a)
          }
        },
        onSeeked: function (e) {
          // console.log('Duration: ', e)
        },
        onPause: function (e) {
          var cmdId = localPro(a, 'pausecmd');
          if (cmdId) {
            doStructCmd(a, cmdId, {});
          }
        }
      });
      a.audio.dom.attr('data-type', a.type).attr('data-path', a.path).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (skin.styles['color']) {
        a.audio.tip.css({
          color: skin.styles['color']
        })
      }
      a.audio.tip.text(localPro(a, 'title', ''))
      if (a.rateProp) {
        a.instance.bindValue(a.rateProp, a, function (e) {
          a.rate(e.data);
        }, true);
      }

      if (localPro(a, 'src')) {
        a.val(localPro(a, 'src'))
      }
      makeValueAndState(a);
    }

    disable(e) {
      $('button', this.audio.controls).prop('disabled', e);
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.value;

      a.value = e;
      if (e == null || e.trim() == '') {
        a.audio.source('');
      } else {
        var x = e.toLowerCase();
        if (x.startWith('http')) {
          a.audio.source(e);
        } else {
          var url;
          if (x.startWith('vr/'))
            url = $E.url + '/' + e;
          else if (x.startWith('/vr/'))
            url = $E.url + e;
          else if (x.startWith('vrs#')) {
            var t2 = e.substring(4);
            var cs = t2.split('||');
            if (cs.length > 1)
              url = $E.vrsUrl + '/GetRecord?id=' + encodeURI(cs[0]) + '&queryType=' + cs[1];
            else
              url = $E.vrsUrl + '/GetRecord?id=' + encodeURI(t2) + '&queryType=record_guid';
          } else if (x.startWith('fs#')) {
            url = $E.url + '/fs/get?file=' + e.substring(3);
          } else
            url = 'http://localhost:9833/recordfile?' + e;
          if (url) {
            a.audio.source(url);
            //console.log('AudioPlayer => ', url);
          }
        }
        if (a.rateProp)
          a.rate(a.instance.getValue(a.rateProp, a.runtime) || 1)
      }
    }

    pause() {
      const a = this,
        cmdId = localPro(a, 'pausecmd');
      if (cmdId) {
        doStructCmd(a, cmdId, {});
      }
      a.audio.pause();
    }


    play() {
      this.audio.play();
    }

    getTotalTime() {
      const val = this.audio.player.duration ? this.audio.player.duration : 0
      return val;
    }

    getCurrentTime() {
      const val = this.audio.player.currentTime ? this.audio.player.currentTime : 0
      return val;
    }

    rate(e) {
      this.audio.rate(e);
    }

    seek(e) {
      this.audio.seek(e);
    }

    restart() {
      this.audio.restart();
    }

    mute(e) {
      this.audio.mute(e);
    }

    /**
     * 根据给定资源播放，支持传递开始播放的秒数和结束的秒数，不传递则表示正常从头播放到尾
     * 如果未传递source，则直接播放之前已经赋值的src，如果之前没有赋值过，则什么都不执行
     * @param options
     *    [source] 播放地址
     *    [starttime] 开始时间（秒数） 默认值0
     *    [endtime] 结束时间（秒数） 默认值0
     */
    splay(options = {}) {
      const {source, starttime = 0, endtime = 0} = options;
      this.audio.pause();
      this.audio.restart();
      let doPlay = () => {
        if (starttime || endtime) {
          if (starttime) {
            this.audio.player.currentTime = starttime;
          }
          let timeupdateCb = () => {
            if (this.audio.player.currentTime > endtime) {
              this.audio.pause();
              $(this.audio.player).off('timeupdate', timeupdateCb);
            }
          };
          if (endtime) {// 如果传递了endtime，才需要去监听timeupdate事件
            $(this.audio.player).on('timeupdate', timeupdateCb);
          }
          this.audio.play();
        } else {
          this.audio.play();
        }
      };

      if (source) {
        $(this.audio.player).one('loadedmetadata', () => {
          doPlay();
        });

        $(this.audio.player).attr("src", source);
      } else {
        if (source === '') {
          $(this.audio.player).attr("src", source);
        } else {
          if (this.audio.canPlay) {
            doPlay();
          }
        }
      }
    }
  }

  /*
	 * 动态页面组件：video
	 */
  class CtlVideo extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      // console.log(a);
      var skin = makeStyles(a);
      if (skin.styles['height'] == 'auto' || !skin.styles['height'])
        skin.styles['height'] = '200px';
      a.dom = a.input = $('<video data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      ;
      a.video = a.input.wisPlayer({
        skin: localPro(a, 'skin') || '',
        //				poster : 'player/echo-hereweare.jpg',
        //				source : 'player/echo-hereweare.mp4',
        //				track : 'player/echo-hereweare.srt'
      })

      a.video.dom.attr('data-type', a.type).attr('data-path', a.path).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (localPro(a, 'src')) {
        a.val(localPro(a, 'src'))
      }
      makeValueAndState(a);
    }

    disable(e) {
      $('button', this.video.controls).prop('disabled', e);
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.value;

      a.value = e;
      if (e == null || e.trim() == '') {
        a.video.source('');
      } else {
        if (e.toLowerCase().startWith('http')) {
          a.video.source(e);
        } else {
          var url;
          if (e.toUpperCase().startWith('RES#')) {
            url = $E.url + e.substring(4);
          } else if (t.startWith('FS#')) {
            url = $E.url + '/fs/get?file=' + e.substring(3);
          }
          if (url) {
            a.video.source(url);
            if ($E.debug)
              console.log('VideoPlayer => ', url);
          }
        }
      }
    }

    pause() {
      this.video.pause();
    }

    play() {
      this.video.play();
    }

    restart() {
      this.video.restart();
    }

    mute() {
      this.video.mute();
    }
  }

  /*
	 * 动态页面组件：progress
	 */
  class CtlProgress extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      // console.log(a);
      var skin = makeStyles(a);
      if (skin.styles['height'] == 'auto' || !skin.styles['height'])
        skin.styles['height'] = '2.2em';

      a.lbStr = localPro(a, 'lbStr');
      a.lbColor = localPro(a, 'lbColor');
      a.spliter = localPro(a, 'spliter');
      a.unit = localPro(a, 'unit');
      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      $('<div class="progress-label" style="position:absolute;left:0;top:4px;right:0;bottom:0;text-align:center;"><span class="label" style="font-weight:bold;text-shadow:1px 1px 0 #fff;line-height:1.2em">Loading...</span></div>').appendTo(a.dom);
      a.label = $('.label', a.dom);

      if (a.spliter) {
        a.dom.progressbar({
          change: function () {
            if (a.lbStr)
              a.label.text(a.lbStr);
            else
              a.label.text(a.dom.progressbar("value") + "%");
          }
        })
        $(".ui-progressbar-value", a.dom).css({
          margin: 0,
          border: 0
        })
        if (skin.styles['color']) {
          a.dom.css('color', '');
          $(".ui-progressbar-value", a.dom).css({
            'background': skin.styles['color']
          })
        }
        if (a.lbColor)
          a.label.css('color', a.lbColor);
      } else {
        a.dom.progressbar({
          change: function () {
            if (a.lbStr)
              a.label.text(a.lbStr);
            else
              a.label.text(a.dom.progressbar("value") + "%");
          }
        })
      }

      makeValueAndState(a);
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.value;

      a.value = e;
      if (a.spliter) {
        if (e instanceof Array) {
          // 获取最大值
          var max = 0;
          $.each(e, function (i, v) {
            max += v.value;
          })
          a.makeSplite(max);
          a.dom.off('resize').on('resize', function () {
            a.makeSplite(max);
          });
        } else {
          $F.err('Ctls - Progress', '不合法的分段数据')
        }
      } else {
        a.dom.progressbar("option", {
          value: e
        })
      }
    }

    makeSplite(max) {
      var a = this,
        bw = a.dom.width(),
        w;
      a.dom.empty();
      $.each(a.value, function (i, v) {
        w = Math.floor(bw * v.value / max);
        $('<span class="sub">' + (v.value + (a.unit || '')) + '</span>')
          .data('entry', v)
          .appendTo(a.dom)
          .css({
            'width': w + 'px',
            'height': '100%',
            'background': v.background,
            'color': v.color,
            'float': 'left',
            'textAlign': 'center'
          })
      })
    }
  }

  /*
	 * 动态页面组件：iframe
	 */
  class CtlIframe extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      // console.log(a);
      var skin = makeStyles(a),
        style = skin.styles;
      if (style['display'] == undefined)
        style['display'] = 'block';
      if (style['width'] == undefined || style['width'] == 'auto')
        style['width'] = '100%';
      a.url = localPro(a, 'url');
      a.dom = $('<iframe data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      ;

      if (a.url)
        a.dom.attr('src', a.url);
      makeValueAndState(a);
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.value;

      a.value = e;
      if (!e.toLowerCase().startWith('http'))
        e = 'http://' + e;
      this.dom.attr('src', e);
    }
  }

  /*
	 * 动态页面组件：slider
	 */
  class CtlSlider extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      // console.log(a);
      var skin = makeStyles(a);
      a.min = localPro(a, 'min', 0);
      a.max = localPro(a, 'max', 100);
      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.dom.slider({
        min: a.min,
        max: a.max,
        slide: function (event, ui) {
          $("#amount").val(ui.value);
        }
      })

      makeValueAndState(a);
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.value;

      a.value = e;
      a.dom.slider({
        value: e
      })
    }

    disable(e) {
      this.dom.slider({
        disabled: e
      });
    }
  }

  /*
	 * 动态页面组件：buttonGroup
	 */
  class CtlButtonGroup extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a);

      skin.clazzs.push('dyn_buttonGroup');
      skin.styles['display'] = skin.styles['display'] || 'inline-block';
      skin.styles['position'] = skin.styles['position'] || 'relative';
      skin.styles['height'] = skin.styles['height'] || '2.2em';
      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);

      var options = {
        items: [],
        visiable: localPro(a, 'visiable', 1)
      };

      // 收集菜单项
      $.each(localPro(a, 'items') || [], function (i, v) {
        var item = {
          label: i18nResource(a, v.l)
        }
        if (v.i) {
          item.icon = {
            type: (v.i.t ? v.i.t : 0),
            name: v.i.n || ''
          }
        }
        if (v.c) {
          item.callback = function () {
            a.trace({
              type: 'click',
              label: item.label
            });
            $F.process(function () {
              doStructCmd(a, v.c, v.p);
            })
          }
        }
        if (v.s) {
          item.state = a.instance.getValue(v.s, a.runtime);
          a.instance.bindValue(v.s, a, function (e) {
            item.state = e.data;
            a.input.state(item);
          })
        } else
          item.state = 0;
        options.items.push(item);
      })
      a.input = a.dom.wisButtonGroup(options);

      makeValueAndState(a);
    }
  }

  /*
	 * 动态页面组件：dropdown
	 */
  class CtlDropdown extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a);

      skin.clazzs.push('dyn_dropdown');
      skin.styles['display'] = skin.styles['display'] || 'inline-block';
      skin.styles['position'] = skin.styles['position'] || 'relative';
      skin.styles['height'] = skin.styles['height'] || '2.2em';
      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);

      var cachekey = 'dyn_dropdown_' + runtime.namespace + '.' + a.id,
        menu = [],
        options = {
          menu: menu,
          defAct: 0,
          hoverable: false,
          fixLayerWidth: (localPro(a, 'auto') ? false : true),
          selected: function (menuIndex) {
            if (!localPro(a, 'nocache')) {
              // console.info('Save dropdown default item: ' + menuIndex);
              CmdUtils.setLocalValue(runtime, cachekey, menuIndex);
            }
          }
        };

      // 收集菜单项
      $.each(localPro(a, 'items', []), function (i, v) {
        var icon = '';
        if (v.i) {
          var cs = v.i.split('#');
          if (cs.length > 1) {
            icon = '<i class="' + $F.fas(cs[0]) + ' fa-fw" style="color:#' + cs[1] + '"></i>';
          } else
            icon = '<i class="' + $F.fas(cs[0]) + ' fa-fw"></i>'
        }
        var label = (v.l ? v.l : 'sep'),
          value = (v.v ? v.v : ''),
          cmd = (v.c ? v.c : ''),
          state = (v.s ? a.instance.getValue(v.s, runtime) : 0);

        var item = {
          id: Guid.raw(),
          icon: icon,
          label: i18nResource(a, label),
          state: state,
          callback: (cmd ? function () {
            doStructCmd(a, cmd, {
              MENU: label,
              VALUE: value
            });
          } : false)
        };
        menu.push(item);
        if (v.s) {
          a.instance.bindValue(v.s, a, function (e) {
            item.state = e.data;
            a.input.state(item);
          })
        }
        if (v.d) {
          options.defAct = i;
          //console.log('Detected dropdown default menu: ' + i);
        }
      });

      if (!localPro(a, 'nocache')) {
        var defAct = parseInt(CmdUtils.getValue(cachekey, options.defAct, runtime));
        if (!isNaN(defAct)) {
          //console.log('Set dropdown personalize menu: ' + defAct);
          options.defAct = defAct;
        }
      }
      if (localPro(a, 'hover'))
        options.hoverable = true;
      if (localPro(a, 'combox'))
        options.comboxStyle = true;
      a.input = a.dom.wisDropdown(options);

      makeValueAndState(a);
    }
  }

  /*
	 * 动态页面组件：dropmenu
	 */
  class CtlDropmenu extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      a.labelProp = localPro(a, 'labelProp');
      a.nodeProp = localPro(a, 'nodeProp');
      //console.log(a);
      var skin = makeStyles(a),
        attrs = '',
        placeholder = localPro(a, 'placeholder', '', true),
        maxLength = localPro(a, 'maxLength'),
        lbStyle = localPro(a, 'lbStyle'),
        labelStr = localPro(a, 'label', '', true);

      if (placeholder)
        attrs += ' placeholder="' + placeholder + '"';
      if (localPro(a, 'required'))
        attrs += ' required="required"';
      if (skin.styles['color']) {
        skin.inputStyles['color'] = skin.styles['color'];
        delete skin.styles['color'];
      }
      if (skin.styles['backgroundColor']) {
        skin.inputStyles['backgroundColor'] = skin.styles['backgroundColor'];
        delete skin.styles['backgroundColor'];
      }
      if (skin.styles['padding']) {
        skin.inputStyles['padding'] = skin.styles['padding'];
        delete skin.styles['padding'];
      }

      if (labelStr) {
        // 带标签控件
        a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
        a.form = $('<div class="ve_form ve_inner" />').appendTo(a.dom);
        a.label = $('<span class="ve_field" />').html(labelStr).appendTo($('<span><span class="ve_z"></span></span>').appendTo(a.form));
        a.input = $('<input type="text" size=1 readonly class="ve_input" ' + attrs + '/>').css(skin.inputStyles).appendTo(a.form);
        if (lbStyle) {
          if (lbStyle.a) {
            if (lbStyle.a == 'justify') {
              skin.labelStyles['textAlignLast'] = lbStyle.a;
            } else {
              skin.labelStyles['textAlign'] = lbStyle.a;
            }
          }
          if (lbStyle.w)
            skin.labelStyles['width'] = lbStyle.w;
          if (lbStyle.c)
            skin.labelStyles['color'] = lbStyle.c;
        }
        a.label.css(skin.labelStyles)
      } else {
        a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
        a.form = $('<div class="ve_inner" />').appendTo(a.dom);
        a.input = $('<input type="text" readonly class="ve_input" ' + attrs + '/>').css(skin.inputStyles).appendTo(a.form);
      }
      a.input.css(skin.inputStyles)
      a.assist = $('<i class="ibtn fa fa-angle-down fa-fw"></i>')
        .appendTo($('<span class="ve_assist"><span class="ve_z"></span></span>').appendTo(a.form))
        .click(function (e) {
          e.preventDefault();
          a.input.trigger('click');
        });
      a.input.on('click', function (e) {
        var source = a.input.data('entity');
        if (!source)
          return;
        if (a.input.data('dropmenu')) {
          e.preventDefault();
          e.stopPropagation();
          return;
        }
        var shell = a.dom.closest('.dyn_box');
        var div = $('<ul class="dropmenu_box" />').appendTo(shell);
        $.each(source, function (i, v) {
          var item = $('<a href="javascript:;" />').text(v.name).data('entity', v);
          $('<li/>').append(item).appendTo(div);
          if (v.folder) {
            item.append('<i class="folder fa fa-angle-right" />');
          }
          a.active(item, a.input);
        })
        a.input.data('dropmenu', div);

        var parentOffset = shell.offset(),
          parentScrollLeft = shell.scrollLeft(),
          parentScrollTop = shell.scrollTop(),
          x = a.input.offset().left - parentOffset.left - parentScrollLeft,
          y = a.input.offset().top + a.input.outerHeight() - parentOffset.top - parentScrollTop + 2;
        div.css({
          left: x + 'px',
          top: y + 'px'
        }).show();
        setTimeout(function () {
          $(document).one('click', function () {
            a.input.removeData('dropmenu');
            $('.dropmenu_box', shell).remove();
          })
        }, 1)
      })

      // id: 树结构的节点ID字段 - 对应数据库结构字段名
      // desc: 树结构节点描述字段 - 对应数据库结构字段名
      // pId: 树结构的父节点ID字段 - 对应数据库结构字段名
      // cacheId: 数据缓存ID
      // sql: 数据库查询SQL KEY
      // 处理动态加载数据选项
      var dataSource = localPro(a, 'dataSource'), resource;
      if (dataSource) {
        if (dataSource.id && dataSource.id !== dataSource.vf) {
          // 兼容旧版本中单独设置了值字段
          dataSource.vf = dataSource.id;
          console.warn('树控件的值字段与id字段不一致。在当前版本中，id字段已被取消，选中时返回值字段（该字段具有必须具有唯一性）');
        }

        if (dataSource.t) {
          // 使用数据变量作为数据源
          if (dataSource.prop) {
            if (dataSource.c) {
              a.instance.bindValue(dataSource.prop, a, function (e) {
                var resource = e.data;
                if (a.precmd) {
                  doStructCmd(a, a.precmd, {
                    DATA: resource
                  })
                }
                a.input.data('entity', resource);
              }, true)
            } else {
              if (dataSource.pid < 1 || dataSource.lf < 1 || dataSource.vf < 1) {
                $F.err('Ctls - Tree', '不合法的树关键列定义');
              } else {
                a.instance.bindValue(dataSource.prop, a, function (e) {
                  var resource = $F.collectTree({
                    fields: e.data
                  }, dataSource.vf - 1, dataSource.pid - 1, dataSource.lf - 1);
                  if (a.precmd) {
                    doStructCmd(a, a.precmd, {
                      DATA: resource
                    })
                  }
                  a.input.data('entity', resource);
                }, true)
              }
            }
          }
        } else {
          // 使用SQL作为数据源
          var cacheKey = dataSource.cache ? (a.runtime.struct.id + '.' + dataSource.cache) : '';
          if (cacheKey && runtime.project.cache[cacheKey]) {
            resource = runtime.project.cache[cacheKey];
            a.input.data('entity', resource);
          } else {
            // 变量传参遍历
            var params = {};
            if (dataSource.binds) {
              $.each(dataSource.binds, function (i, v) {
                params['C' + (i + 1)] = a.instance.getValue(v, runtime);
                a.instance.bindValue(v, a, function (e) {
                  params['C' + (i + 1)] = e.data;
                  a.exeSql(dataSource, params, cacheKey);
                });
              });
            }
            a.exeSql(dataSource, params, cacheKey);
          }
        }
      }

      makeValueAndState(a);
      if (a.labelProp) {
        a.instance.bindValue(a.labelProp, a, function (e) {
          a.val(e.data);
        }, true);
      }
    }

    exeSql(dataSource, params, cacheKey) {
      var a = this;
      a.runtime.sqlQueue[dataSource.sql] = {
        params: params,
        callback: function (data) {
          // console.log(data);
          var paramsStr = $F.display(params);
          if (data.count < 0) {
            $F.err('SQLExec - failed', 'DYN[' + a.runtime.struct.id + '] KEY[' + dataSource.sql + '], 控件[' + a.type + '@' + a.path + '][PARAMS: ' + paramsStr + ']');
          } else {
            if ($E.debug)
              console.log('SQLExec - success', 'DYN[' + a.runtime.struct.id + '] KEY[' + dataSource.sql + '], 控件[' + a.type + '@' + a.path + '][PARAMS: ' + paramsStr + ']');
            // 进行树整理
            try {
              var resource = $F.collectTree(data, dataSource.vf, dataSource.pid, dataSource.lf);
              if (a.precmd) {
                doStructCmd(a, a.precmd, {
                  DATA: resource
                })
              }
              if (cacheKey)
                a.instance.project.cache[cacheKey] = resource;
              a.input.data('entity', resource);
            } catch (e) {
              console.error(e);
            }
          }
        }
      }
    }

    active(target, parent) {
      var a = this;
      target.on('click', function (e) {
        var source = $(e.currentTarget).data('entity');
        e.preventDefault();
        e.stopPropagation();

        if (source.folder) {
          if (parent.data('sub'))
            parent.data('sub').remove();
          var shell = a.dom.closest('.dyn_box');
          var div = $('<ul class="dropmenu_box dropmenu_box_sub" />').appendTo(shell);
          $.each(source.children, function (i, v) {
            var item = $('<a href="javascript:;" />').text(v.name).data('entity', v);
            $('<li/>').append(item).appendTo(div);
            if (v.folder) {
              item.append('<i class="folder fa fa-angle-right" />');
            }
            a.active(item, div);
          })
          parent.data('sub', div);

          var parentOffset = shell.offset(),
            parentScrollLeft = shell.scrollLeft(),
            parentScrollTop = shell.scrollTop(),
            x = target.offset().left + target.outerWidth() - parentOffset.left - parentScrollLeft,
            y = target.offset().top + -parentOffset.top - parentScrollTop + 2;
          div.css({
            left: x + 'px',
            top: y + 'px'
          }).show();
        } else {
          $(document).click();
          var real = (source.data === undefined ? source.id : source.data);
          if (a.dataProp)
            a.value = a.instance.getValueByPropertyName(a.runtime, a.dataProp, real);
          else
            a.value = real;
          if (a.labelProp) {
            a.instance.fireValue(a.labelProp, a.runtime, {
              type: 0,
              data: source.name
            }, a)
          }
          a.input.data('id', a.value).val(source.name).trigger('change');
          if (a.nodeProp) {
            var node = $.extend({}, source);
            if (node.parent) {
              node.parent = $.extend({}, node.parent);
              delete node.parent.children;
              delete node.parent.parent;
            }
            delete node.children;
            a.instance.fireValue(a.nodeProp, a.runtime, {
              type: 0,
              data: node
            }, a)
          }
        }
      })
    }

    val(e) {
      var a = this;
      if (!arguments.length) {
        return a.input.data('id');
      } else {
        a.value = e;
        a.input.data('id', e);
      }
    }
  }

  /*
	 * 动态页面组件：list
	 */
  class CtlList extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      var skin = makeStyles(a);
      if (!skin.styles['border'])
        skin.styles['border'] = 0;
      a.changeCalls = []; // 选择改变时的回调方法队列

      a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (parseInt(a.dom.css('flex-grow'))) {
        // 用于处理弹性布局单元
        // 当控件处于弹性单元时，应该将对应的宽或者高的值去除
        if (a.dom.parent().css('flex-direction') == 'column')
          a.dom.css('height', '');
        else
          a.dom.css('width', '');
        //if (H5Utils.isChromeLegacy)
        a.box = $('<div class="wrapper" />').appendTo(a.dom);
      }

      a.input = a.box.wisList({
        lang: $E.lang,
        multiSelect: localPro(a, 'multi', 1),
        checkable: localPro(a, 'checkable', 1),
        actionBar: localPro(a, 'actbtn', 1),
        tabIndex: 0,
        onChange: function (data) {
          $.each(a.changeCalls, function () {
            this();
          })
        }
      });
      a.defaultValue = '';

      var dataSource = localPro(a, 'dataSource', {});
      if (!dataSource.type) {
        // 处理预加载数据选项
        var cs = [];
        for (var key in dataSource.options) {
          cs.push({
            label: dataSource.options[key],
            value: key
          })
        }
        a.setOptions(cs);
        if (!a.dataProp)
          a.val('');
      } else if (dataSource.type == 1) {
        // 处理动态加载数据选项
        if (dataSource.options && dataSource.options.sql) {
          var vf = dataSource.options.key,
            lf = dataSource.options.value,
            cachekey = dataSource.options.cache,
            sql = dataSource.options.sql,
            binds = dataSource.options.binds || [];

          if (cachekey)
            cachekey = runtime.struct.id + '.' + cachekey;
          if (cachekey && runtime.project.cache[cachekey]) {
            a.setOptions(getDataFromSql(runtime.project.cache[cachekey], vf, lf));
          } else {
            a.waitForOptions = true;
            // 变量传参遍历
            var params = {};
            $.each(binds, function (i, v) {
              if (v.startWith('@')) {
                var x = parseInt(v.substring(1));
                params['C' + (i + 1)] = a.xData[x - 1];
                a.xitemListener = a.xitemListener || {};
                a.xitemListener[x] = function (e) {
                  params['C' + (i + 1)] = e;
                  exeSql(a, cachekey, sql, params, vf, lf, a.input.val());
                }
              } else {
                params['C' + (i + 1)] = a.instance.getValue(v, runtime);
                a.instance.bindValue(v, a, function (e) {
                  params['C' + (i + 1)] = e.data;
                  exeSql(a, cachekey, sql, params, vf, lf, a.dataProp ? a.instance.getValue(a.dataProp, runtime) : a.input.val());
                });
              }
            });
            exeSql(a, cachekey, sql, params, vf, lf, (a.dataProp ? a.instance.getValue(a.dataProp, runtime) : undefined));
          }
        }
      } else if (dataSource.type == 2) {
        // 处理全局SQL数据项
        var vf = dataSource.options.key,
          lf = dataSource.options.value,
          cachekey = dataSource.options.cache,
          sql = dataSource.options.sql,
          params = [];
        $F.ognl(sql, function (propertyName) {
          params.push(propertyName);
        })
        var ognlOptions = function (value) {
          var jsonSql = $F.ognl(sql, function (propertyName) {
            if (propertyName.startWith('@')) {
              var x = parseInt(propertyName.substring(1));
              return a.xData[x - 1];
            } else {
              return getValue(a.instance, propertyName, a.runtime);
            }
          })
          var data = a.instance.project.queryCodetable(cachekey, jsonSql);
          a.setOptions(getDataFromJsonSql(data, vf, lf), value)
        }
        if (params.length > 0) {
          // 使用变量入参
          $.each(params, function (i, v) {
            if (v.startWith('@')) {
              a.xitemListener = a.xitemListener || {};
              a.xitemListener[parseInt(v.substring(1))] = function (e) {
                ognlOptions(a.input.val())
              }
            } else {
              a.instance.bindValue(v, a, function (e) {
                ognlOptions(a.dataProp ? a.instance.getValue(a.dataProp, runtime) : a.input.val())
              })
            }
          })
        }
        ognlOptions(a.dataProp ? a.instance.getValue(a.dataProp, runtime) : undefined);
      } else if (dataSource.type == 3) {
        // 处理绑定变量的数据源
        var options = dataSource.options;
        // console.log(options)
        if (options && options.n) {
          a.instance.bindValue(options.n, a, function (e) {
            if (e.type)
              return;
            if (!options.t)
              a.setOptions(getDataFromProperty(e.data), (a.dataProp ? a.instance.getValue(a.dataProp, runtime) : a.input.val()));
            else
              a.setOptions(getDataFromProperty(e.data, options.v, options.l), (a.dataProp ? a.instance.getValue(a.dataProp, runtime) : a.input.val()));
          })
          var data = a.instance.getValue(dataSource.options.n, runtime);
          if (data) {
            if (!options.t)
              a.setOptions(getDataFromProperty(data), (a.dataProp ? a.instance.getValue(a.dataProp, runtime) : undefined));
            else
              a.setOptions(getDataFromProperty(data, options.v, options.l), (a.dataProp ? a.instance.getValue(a.dataProp, runtime) : undefined));

          }
        }
      }

      makeValueAndState(a);
    }

    setOptions(data, value) {
      const a = this;
      a.waitForOptions = false;
      a.input.setData(data);
      a.val((value == undefined) ? a.defaultValue : value);
    }

    bind(e, callback) {
      if (e == 'change')
        this.changeCalls.push(callback);
      else
        this.input.bind(e, callback);
    }

    val(e) {
      var a = this;
      if (e === undefined) {
        var labelProp = localPro(this, 'labelProp');
        var l = [],
          v = [];
        $.each(a.input.val(), function () {
          l.push(this.label);
          v.push(this.value);
        })
        if (labelProp) {
          a.instance.fireValue(labelProp, a.runtime, {
            type: 0,
            data: l.join(',')
          }, a)
        }
        return v.join(',');
      } else {
        if (!a.waitForOptions) {
          a.input.clear()
          if (e != null) {
            if ($.type(e) == 'string') {
              var s = e.split(',');
              a.input.select(s);
            } else
              a.input.select([e]);
          }
          if (labelProp) {
            var l = [];
            $.each(a.input.val(), function () {
              l.push(this.label);
            })
            a.instance.fireValue(labelProp, a.runtime, {
              type: 0,
              data: l.join(',')
            })
          }
        } else
          a.defaultValue = e;
      }
    }

    reset() {
      if (this.dataProp) {
        var v = this.instance.getValue(this.dataProp, this.runtime);
        this.val(v);
        $.each(this.changeCalls, function () {
          this({}, v)
        })
      }
    }

    checkAll() {
      this.input.selectAll();
    }

    destroy() {
      this.input.destroy();
      super.destroy();
    }
  }

  /*
	 * 动态页面组件：tree
	 */
  class CtlTree extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      const a = this;
      //console.log(a);
      a.labelProp = localPro(a, 'labelProp');
      a.nodeProp = localPro(a, 'nodeProp');
      a.iconcmd = localPro(a, 'iconcmd');
      a.precmd = localPro(a, 'precmd');
      a.newcmd = localPro(a, 'newcmd');
      a.editcmd = localPro(a, 'editcmd');
      a.predelcmd = localPro(a, 'predelcmd');
      a.delcmd = localPro(a, 'delcmd');
      a.incmd = localPro(a, 'incmd');
      a.outcmd = localPro(a, 'outcmd');
      a.idxcmd = localPro(a, 'idxcmd');
      a.oprcmd = localPro(a, 'oprcmd');
      a.selectCmd = localPro(a, 'selectCmd');
      a.multi = localPro(a, 'multi');
      a.draggable = localPro(a, 'draggable');
      a.cross = localPro(a, 'cross');
      a.indent = localPro(a, 'indent');
      a.lock = localPro(a, 'lock');
      a.maxLevel = localPro(a, 'maxLevel');
      a.icons = localPro(a, 'icons');
      a.changeCalls = []; // 选择改变时的回调方法队列

      var skin = makeStyles(a);
      skin.styles['overflow'] = 'auto';
      if (!skin.styles['border'])
        skin.styles['border'] = 0;
      a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (parseInt(a.dom.css('flex-grow'))) {
        // 用于处理弹性布局单元
        // 当控件处于弹性单元时，应该将对应的宽或者高的值去除
        if (a.dom.parent().css('flex-direction') == 'column') {
          a.dom.css('height', '');
          a.box = $('<div class="wrapper" style="height:100%" />').appendTo(a.dom);
        } else {
          a.dom.css('width', '');
          a.box = $('<div class="wrapper" style="height:100%;width:100%" />').appendTo(a.dom);
        }
        //if (H5Utils.isChromeLegacy)
      }

      var menuOpr = {};
      if (a.newcmd)
        menuOpr.add = true;
      if (a.editcmd)
        menuOpr.edit = true;
      if (a.delcmd)
        menuOpr.remove = true;
      if (a.indent) {
        menuOpr.indent = true;
        menuOpr.protrude = true;
      }
      var options = {
        lang: $E.lang || 'zh_CN',
        showCheck: a.multi,
        showCheckAll: (localPro(a, 'showCheckAll') ? false : true),
        expandAll: (localPro(a, 'expendAll') ? true : false),
        includeFolder: (localPro(a, 'fcbu') ? true : false),
        includeHalfState: (localPro(a, 'halfble') ? false : true),
        cascadeCheck: (localPro(a, 'nocase') ? false : true),
        folderSelectable: (localPro(a, 'fsab') ? false : true),
        menuOpr: menuOpr,
        newtip: localPro(a, 'addLabel', ''),
        draggable: a.draggable,
        cross: a.cross,
        lock: a.lock,
        maxLevel: a.maxLevel,
        tabIndex: 0,
        completely: (localPro(a, 'async') ? false : true),
        labels: function (t) {
          return t.label || t.name;
        },
        awesome: (a.icons && a.icons.t) ? {
          type: a.icons.t,
          folder: a.icons.f,
          leaf: a.icons.l
        } : false,
        onMenuOprInit: function (node, opr) {
          var level = 1,
            parent = node.parent;
          while (parent) {
            level++;
            parent = parent.parent;
          }
          if (level == 1)
            opr.indent = false;
          if ($.isFunction(a.menuOprCalback)) {
            return a.menuOprCalback(node, level, opr);
          } else {
            if (a.oprcmd) {
              var EVENT = {
                NODE: node,
                LEVEL: level,
                OPR: opr
              }
              doStructCmd(a, a.oprcmd, EVENT);
              return EVENT.OPR;
            } else {
              if (level >= a.maxLevel && a.maxLevel > 0) {
                delete opr.add;
                delete opr.protrude;
              }
              return opr;
            }
          }
        },
        onSelected: function (data, li) {
          if (a.selectCmd && data) {
            if (localPro(a, 'fsab') && data.folder)
              return false;
            doStructCmd(a, a.selectCmd, {
              DATA: data
            });
          }
          if (a.multi)
            return;

          var label = '';
          if (data == undefined) {
            // 等同于onUnselected事件
            a.value = '';
          } else {
            if (localPro(a, 'fsab') && data.folder)
              return false;
            label = data.name;

            if (a.dataProp)
              a.value = a.instance.getValueByPropertyName(a.runtime, a.dataProp, data.id);
            else
              a.value = data.id;
            if (a.labelProp) {
              a.instance.fireValue(a.labelProp, a.runtime, {
                type: 0,
                data: label
              }, a)
            }
            if (a.nodeProp) {
              var node = JSON.parse(JSON.stringify(data, ['id', 'name', 'pId', 'folder', 'parent', 'entity']));
              a.instance.fireValue(a.nodeProp, a.runtime, {
                type: 0,
                data: node
              }, a)
            }
          }
          $.each(a.changeCalls, function () {
            this();
          })
        },
        onChecked: function (data) {
          var values = [],
            labels = [];
          for (var i = 0; i < data.length; i++) {
            if (!localPro(a, 'fcbu') && data[i].folder)
              continue;
            values.push(data[i].id);
            labels.push(data[i].name)
          }

          a.value = values.join(',');
          if (a.labelProp) {
            a.instance.fireValue(a.labelProp, a.runtime, {
              type: 0,
              data: labels.join(',')
            }, a)
          }
          $.each(a.changeCalls, function () {
            this();
          })
        },
        onAddnew: function (data, callback) {
          if (a.newcmd) {
            if ($.isFunction(data)) {
              callback = data;
              data = undefined;
            }
            doStructCmd(a, a.newcmd, {
              PARENT: data,
              callback: function (child) {
                child.pId = (data == undefined ? '' : data.id);
                child.flag = 2;
                callback(child);
              }
            });
          }
        },
        onEdit: function (data, callback, el, menuOpr) {
          if (a.editcmd) {
            doStructCmd(a, a.editcmd, {
              DATA: data,
              EL: el,
              OPR: menuOpr,
              callback: function (child) {
                if (child.flag !== 2)
                  child.flag = 1;
                callback(child);
              }
            });
          }
        },
        preRemove: function (data, parentData) {
          if (a.predelcmd) {
            var R = doStructCmd(a, a.predelcmd, {
              DATA: data,
              PARENT: parentData,
              STOP: 0
            });
            return (R.EVENT.STOP ? false : true);
          }
          return false;
        },
        onRemoved: function (data, parentData) {
          var resource = a.input.getData();
          if (!resource.removed)
            resource.removed = [];
          resource.removed.push(data);
          if (a.delcmd) {
            doStructCmd(a, a.delcmd, {
              DATA: data,
              PARENT: parentData
            });
          }
        },
        onMoveIn: function (data, parentData) {
          data.pId = parentData.id;
          // console.log(data.name + ' move in to ' + (parentData.name || '<root>'));
          if (a.incmd) {
            doStructCmd(a, a.incmd, {
              CHILD: data,
              PARENT: parentData
            });
          }
        },
        onMoveOut: function (data, parentData) {
          // console.log(data.name + ' move out from ' + (parentData.name || '<root>'));
          if (a.outcmd) {
            doStructCmd(a, a.outcmd, {
              CHILD: data,
              PARENT: parentData
            });
          }
        },
        onIndex: function (children) {
          if (a.idxcmd) {
            doStructCmd(a, a.idxcmd, {
              CHILDREN: children
            })
          }
        }
      };
      if (a.iconcmd) {
        options.icons = function (node) {
          var EVENT = {
            NODE: node
          };
          doStructCmd(a, a.iconcmd, EVENT);
          return EVENT.ICON;
//					return doStructCmd(a, a.iconcmd, {
//						NODE: t
//					})
        }
      }
      a.input = a.wisTree = a.box.wisTree(options);

      // id: 树结构的节点ID字段 - 对应数据库结构字段名
      // name: 树结构节点描述字段 - 对应数据库结构字段名
      // pId: 树结构的父节点ID字段 - 对应数据库结构字段名
      // everynode: 1=只能选末级节点
      // mSel: 0/1 是否允许多选
      // cacheId: 数据缓存ID
      // sql: 数据库查询SQL KEY
      // 处理动态加载数据选项
      var dataSource = localPro(a, 'dataSource'), resource;
      if (dataSource) {
        if (dataSource.id && dataSource.id !== dataSource.vf) {
          // 兼容旧版本中单独设置了值字段
          dataSource.vf = dataSource.id;
          console.warn('树控件的值字段与id字段不一致。在当前版本中，id字段已被取消，选中时返回值字段（该字段具有必须具有唯一性）');
        }
        if (dataSource.t) {
          // 使用数据变量作为数据源
          if (dataSource.prop) {
            if (dataSource.c) {
              a.instance.bindValue(dataSource.prop, a, function (e) {
                var resource = e.data;
                if (a.precmd) {
                  doStructCmd(a, a.precmd, {
                    DATA: resource
                  })
                }
                a.input.setData(resource);
              }, true)
            } else {
              if (dataSource.pid < 1 || dataSource.lf < 1 || dataSource.vf < 1) {
                $F.err('Ctls - Tree', '不合法的树关键列定义');
              } else {
                a.instance.bindValue(dataSource.prop, a, function (e) {
                  var resource = $F.collectTree({
                    fields: e.data
                  }, dataSource.vf - 1, dataSource.pid - 1, dataSource.lf - 1);
                  if (a.precmd) {
                    doStructCmd(a, a.precmd, {
                      DATA: resource
                    })
                  }
                  a.input.setData(resource);
                }, true)
              }
            }
          }
        } else {
          // 使用SQL作为数据源
          var cacheKey = dataSource.cache ? (a.runtime.struct.id + '.' + dataSource.cache) : '';
          if (cacheKey && runtime.project.cache[cacheKey]) {
            resource = runtime.project.cache[cacheKey];
            a.input.setData(resource);
            if (a.dataProp)
              a.val(a.instance.getValue(a.dataPrpo, runtime));
          } else {
            // 变量传参遍历
            var params = {};
            if (dataSource.binds) {
              $.each(dataSource.binds, function (i, v) {
                params['C' + (i + 1)] = a.instance.getValue(v, runtime);
                a.instance.bindValue(v, a, function (e) {
                  params['C' + (i + 1)] = e.data;
                  a.exeSql(dataSource, params, cacheKey);
                });
              });
            }
            a.exeSql(dataSource, params, cacheKey);
          }
        }
      } else
        a.input.setData([])

      makeValueAndState(a);
    }

    exeSql(dataSource, params, cacheKey) {
      var a = this;
      a.runtime.sqlQueue[dataSource.sql] = {
        params: params,
        callback: function (data) {
          // console.log(data);
          var paramsStr = $F.display(params);
          if (data.count < 0) {
            $F.err('SQLExec - failed', 'DYN[' + a.runtime.struct.id + '] KEY[' + dataSource.sql + '], 控件[' + a.type + '@' + a.path + '][PARAMS: ' + paramsStr + ']');
          } else {
            if ($E.debug)
              console.log('SQLExec - success', 'DYN[' + a.runtime.struct.id + '] KEY[' + dataSource.sql + '], 控件[' + a.type + '@' + a.path + '][PARAMS: ' + paramsStr + ']');
            // 进行树整理
            try {
              var resource = $F.collectTree(data, dataSource.vf, dataSource.pid, dataSource.lf);
              if (a.precmd) {
                doStructCmd(a, a.precmd, {
                  DATA: resource
                })
              }
              if (cacheKey)
                a.instance.project.cache[cacheKey] = resource;
              a.input.setData(resource);
            } catch (e) {
              console.error(e);
            }
            if (a.dataProp)
              a.val(a.instance.getValue(a.dataProp, a.runtime));
          }
        }
      }
    }

    bind(e, callback) {
      if (e == 'change')
        this.changeCalls.push(callback);
      else
        this.wisTree.tree.bind(e, callback);
    }

    val(e) {
      var a = this;
      if (!arguments.length)
        return a.value;

      if ((a.value || '') == (e || ''))
        return;
      a.value = e || '';
      if (a.multi) {
        a.input.clearCheck();
        //console.log('Set: ' + e)
        var s = e.toString().split(',');
        $.each(s, function (i, v) {
          a.input.check(v);
        })
        a.input.triggerChecked();
      } else {
        a.input.select(e);
      }
    }

    filter(e, reverse) {
      this.input.filter(e, reverse);
    }

    filterById(e, reverse) {
      if (this.realData)
        this.input.setData(this.realData);
      this.input.filterById(e, reverse);
    }

    /**
     * 截取某个子节点作为树展示
     * @param id
     */
    subNodeById(id) {
      var a = this;
      if (!a.realData)
        a.realData = a.input.getData();

      var getNodeById = function (nodes, id) {
        for (var i = 0; i < nodes.length; i++) {
          // console.log(nodes[i]);
          if (nodes[i].id == id)
            return nodes[i];
          if (nodes[i].folder && nodes[i].children) {
            var v = getNodeById(nodes[i].children, id);
            if (v)
              return v;
          }
        }
        return null;
      }
      var target = getNodeById(a.realData.children, id);
      if (target)
        a.input.setData({
          folder: true,
          children: [target]
        });
      else
        a.input.setData([]);
    }

    search(e) {
      var a = this,
        li = a.input.search(e);
      if (li) {
        if (li.offset().top < a.dom.offset().top || li.offset().top > (a.dom.offset().top + a.dom.height()))
          a.dom.scrollTop(li.offset().top - a.dom.offset().top + a.dom.scrollTop());
      }
    }

    redraw(params) {
      this.input.redraw({
        menuOpr: {
          add: params.newcmd,
          edit: params.editcmd,
          remove: params.delcmd,
          indent: params.indent,
          protrude: params.protrude
        },
        crosslevel: params.indent
      });
    }

    dataMap() {
      var map = {};

      function c(data) {
        $.each(data.children, function (i, v) {
          map[v.id] = v;
          if (v.folder)
            c(v);
        })
      }

      c(this.realData || this.input.getData());
      return map;
    }

    destroy() {
      this.input.destroy();
      super.destroy();
    }

    get data() {
      return this.input.getData().children;
    }

    get source() {
      let cs = [];

      function collector(children) {
        $.each(children, function (i, v) {
          let item = $.extend({}, v);
          delete item.parent;
          delete item.children;
          cs.push(item);
          if (v.folder)
            collector(v.children);
        })
      }

      collector(this.input.getData().children);
      return cs;
    }
  }

  /*
	 * 动态页面组件：Table
	 */
  class CtlTable extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      const a = this;
      //console.log(a);
      a.newcmd = localPro(a, 'newcmd');
      a.vicmd = localPro(a, 'vicmd');
      a.rmcmd = localPro(a, 'rmcmd');
      a.editcmd = localPro(a, 'editcmd');
      a.sortcmd = localPro(a, 'sortcmd');
      a.filtecmd = localPro(a, 'filtecmd');
      a.movecmd = localPro(a, 'movecmd');
      a.dragcmd = localPro(a, 'dragcmd');
      a.cellcmd = localPro(a, 'cellcmd');
      a.rowcmd = localPro(a, 'rowcmd');
      a.percentSplit = localPro(a, 'percentSplit');

      let skin = makeStyles(a),
        border = skin.styles['border'] || false,
        tableHeight,
        uniqueId = $F.randomString(16);
      delete skin.styles['border'];
      a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '" id="' + uniqueId + '" />').appendTo(container);
      a.dom.addClass(skin.clazzs.join(' ')).css(skin.styles)

      if (parseInt(a.dom.css('flex-grow'))) {
        // 用于处理弹性布局单元
        // 当控件处于弹性单元时，应该将对应的宽或者高的值去除
        if (a.dom.parent().css('flex-direction') == 'column')
          a.dom.css('height', '');
        else
          a.dom.css('width', '');
        //if (H5Utils.isChromeLegacy)
        a.box = $('<div class="wrapper" />').appendTo(a.dom);
        tableHeight = '100%';
      } else {
        // 当wrapper的高度为自动时，表格高度也为自动
        // 否则表格高度为100%
        tableHeight = (getProp(a, 'styles', 'height', 'auto') !== 'auto') ? '100%' : 'auto';
      }
      a.dom.attr('id', uniqueId);

      a.value = false;
      a.columns = [];
      a.translater = {}; // 用于翻译的数组
      a.staticCols = 0; // 当前表格的静态列
      a.flagCols = 0; // 当前表格中绘制的特殊列
      a.changeCalls = []; // 行选择改变时的回调方法队列
      a.dblclickCalls = []; // 行双击时的回调方法队列
      a.radios = {};
      a.clickableEventCallbacks = {};
      a.makeColumnsInfo();

      if (a.columns.length > 0) {
        let selectCallback = function (data, row, e) {
          $.each(a.changeCalls, function () {
            this(e, {
              GRIDROW: row + 1,
              ROWDATA: data
            });
          })
        };

        a.options = {
          debug: $E.debug,
          lang: $E.lang,
          height: tableHeight,
          border: border,
          columns: a.columns,
          percentSplit: a.percentSplit,
          cookie: (localPro(a, 'cookie') ? ('wisGrid.' + runtime.struct.id + '.' + a.id) : ''),
          sortable: localPro(a, 'sortable'),
          sort: localPro(a, 'sortfield'),				// 排序的列名
          direct: localPro(a, 'sortdirect', 'asc'),	// 排序方式
          dynamicLines: (a.newcmd ? 0 : localPro(a, 'disnum', 0)),
          frozen: localPro(a, 'frozen', 0),
          tabIndex: 0,
          lockCols: a.flagCols,
          hideLine: localPro(a, 'hideline', false),
          hideColumnMenu: localPro(a, 'hidemenu', false),
          wrappable: localPro(a, 'wrappable', false),
          emptyLine: (a.newcmd ? true : false),
          emptyTip: (a.newcmd ? false : true),
          movable: (a.movecmd ? true : false),
          draggable: (a.dragcmd ? true : false),
          copySep: localPro(a, 'copysep'),
          clickableEventCallbacks: $.extend(a.clickableEventCallbacks, {
            edit: function (target, td, tr, e) {
              const gridData = a.wisGrid.getData(),
                row = parseInt(tr.attr('data-row')),
                rowData = gridData[row],
                cmd = target.attr('data-cmd');
              if (cmd) {
                $F.process(function () {
                  doStructCmd(a, cmd, {
                    GRIDROW: row + 1,
                    ROWDATA: rowData,
                    finish: function () {
                      a.wisGrid.refresh(row)
                    }
                  })
                })
              }
              a.trace({
                type: 'edit',
                data: JSON.stringify(rowData)
              })
            },
            remove: function (target, td, tr, e) {
              const gridData = a.wisGrid.getData(),
                row = parseInt(tr.attr('data-row')),
                rowData = gridData[row],
                cmd = target.attr('data-cmd');
              doStructCmd(a, cmd, {
                GRIDROW: row + 1,
                ROWDATA: rowData,
                remove: function () {
                  a.wisGrid.removeData(row);
                  if (rowData.flag !== 2) {
                    if (!gridData.removed)
                      gridData.removed = [];
                    gridData.removed.push(rowData);
                  }
                  if (a.dataProp) {
                    a.instance.fireValue(a.dataProp, runtime, {
                      type: 0,
                      data: gridData
                    }, a)
                  }
                }
              });
              a.trace({
                type: 'remove',
                data: JSON.stringify(rowData)
              })
            }
          }),
          plains: function (data, row, col) {
            if (!data || col < 0)
              return '';

            var column = a.columns[col];
            if (column.flag == 1 || column.flag == 2)
              return '';
            var cells = profile.props.local.cells,
              cellInfo = cells[column.defCol],
              cellData = data[column.realCol],
              translateKey = 'T' + column.defCol;
            if (cellInfo === undefined)
              return '';

            switch (cellInfo.t) {
              case 6: // radio
              case 7: // checkbox
              case 8:
              case 13: // 动态输入
                return cellData;
              case 10:
                let exp = $F.ognl(cellInfo.extend.exp, function (propertyName) {
                  return a.instance.getValue(propertyName, a.runtime);
                })
                return CtlUtils.expression(cellData, exp, a.runtime);
              case 12:
                // 链接
                if (cellInfo.extend.icon && cellInfo.extend.icon.label)
                  return cellInfo.extend.icon.label;
                else
                  return cellData;
              case 14:
                // 动态扩展列，内容为扩展字段绑定的交叉表第一行
                var rs = a.wisGrid.getData();
                if (rs.length > 0) {
                  for (var i = 0; i < rs[0].length; i++) {
                    if (rs[0][i] == column.crossCol) {
                      column.realCol = i;
                      cellData = data[column.realCol];
                      //console.log('找到匹配的交叉列： ' + column.crossCol + ', col=' + i, cellData)
                      break;
                    }
                  }
                  if (column.realCol > -1) {
                    if (column.editType == 5 || column.editType == 6) {
                      var relSql = column.selectCol || '';
                      if (relSql) {
                        a.doPubSql(relSql.trim(), column.editType == 6);
                        translateKey = 'P' + relSql;
                      }
                    }
                  } else {
                    if (rs.length > 0) {
                      if (rs[0].length == 0) {
                        // 交叉表无数据，造成0行字段没有静态列
                        rs[0][a.staticCol + 1] = column.crossCol;
                        column.realCol = a.staticCol + 1;
                      } else {
                        rs[0].push(column.crossCol);
                        column.realCol = rs[0].length - 1;
                      }
                      //console.log('交叉列取值匹配失败： ' + column.crossCol + ', 创建列=' + column.realCol)
                    }
                  }
                }
                if (cellData == undefined) {
                  if (column.editType == 1)
                    cellData = data[column.realCol] = 0;
                  else
                    cellData = data[column.realCol] = '';
                }
            }

            // 对需要翻译的字段进行翻译
            var tra = a.translater[translateKey];
            if (tra) {
              //var old = cellData;
              if (a.multiEntity && a.multiEntity.contains(translateKey)) {
                // 下拉多选值
                if ($.type(cellData) == 'string') {
                  var cs = [];
                  $.each(cellData.split(','), function (i, v) {
                    if (tra[v] !== undefined)
                      cs.push(tra[v]);
                  })
                  cellData = cs.join(',')
                } else
                  cellData = tra[cellData];
              } else
                cellData = tra[cellData];
              // console.log('[' + realCol + '] ' + old + ' -> ' + cellData);
            }

            if (cellData == undefined || cellData === '')
              return '';
            else {
              if ($.type(cellData) == 'object') {
                if (cellData.html)
                  return $(cellData.html).text();
                else {
                  //	if ($.type(cellData) == 'string' && cellData == '1900-01-01 00:00:00')
                  //		return '';
                  //	else
                  return cellData.toString();
                }
              } else {
                let exp = $F.ognl(cellInfo.e, function (propertyName) {
                  return a.instance.getValue(propertyName, a.runtime);
                })
                return CtlUtils.expression(cellData, exp, a.runtime);
              }
            }
          },
          models: function (data, row, col) {
            if (!data)
              return '';

            const column = a.columns[col];
            if (column.flag == 1 || column.flag == 2) {
              if (data._summary)
                return '';
              // 绘制控件内置列
              let tip = (column.flag == 1 ? $F.lang('message.edit') : $F.lang('message.remove')),
                ico = (column.flag == 1 ? 'edit' : 'trash-alt'),
                clickable = (column.flag == 1 ? 'edit' : 'remove'),
                cmd = (column.flag == 1 ? a.vicmd : a.rmcmd);
              return {
                html: '<a href="javascript:;" title="' + tip + '" class="dyn_link dyn_er_flag wisgrid_clickable" data-clickevent="' + clickable + '" data-cmd="' + cmd + '"><i class="' + $F.fas(ico) + '"></i></a>',
              }
            }

            let cells = profile.props.local.cells,
              cellInfo = cells[column.defCol] || {t: 0},
              cellData = data[column.realCol],
              translateKey = 'T' + column.defCol;

            if (a.modelsCallback && !a.modelsCallback(row, column, cellInfo, data))
              return '';

            switch (cellInfo.t) {
              case 6:
                if (data._summary)
                  return '';
                // 单选选项
                var classes = 'wisgrid_clickable js_rb_' + col,
                  name = a.path.replaceAll('\\.', '_') + '_rb_' + column.realCol,
                  checked = '';
                if (cellData) {
                  // 单选列，不允许多行数据为1，自动将后续的行置为0
                  if (a.radios['C' + column.realCol] == undefined) {
                    checked = 'checked'
                    a.radios['C' + column.realCol] = row;
                  } else {
                    data[column.realCol] = 0;
                  }
                }
                return {
                  html: '<input type="radio" name="' + name
                    + '" class="' + classes
                    + '" data-clickevent="' + column.name
                    + '" ' + checked + '>'
                }
              case 7:
                if (data._summary)
                  return '';
                // 多选选项
                var classes = 'wisgrid_clickable js_ckb_' + col,
                  name = a.path.replaceAll('\\.', '_') + '_ckb_' + column.realCol;
                var disabledVal = false;
                var checkedVal = false;
                if (cellData === -1) {
                  disabledVal = true;
                } else if (cellData === 0 || cellData === '') {
                  checkedVal = false;
                } else {
                  checkedVal = true;
                }
                return {
                  html: '<input type="checkbox" name="' + name
                    + '" class="' + classes
                    + ((cellInfo.extend && cellInfo.extend.cmd) ? ('" data-cmd="' + cellInfo.extend.cmd) : '')
                    + '" data-clickevent="' + column.name
                    + '" ' + (checkedVal ? 'checked' : '') + (disabledVal ? 'disabled' : '') + '>'
                }
              case 8:
                // 根据列值决定行颜色
                var x = {};
                if (cellData) {
                  var cs = cellData.split('||');
                  if (cs[0])
                    x.background = cs[0];
                  if (cs.length > 1)
                    x.color = cs[1];
                }
                return x;
              case 10:
                // 公式
                let exp = $F.ognl(cellInfo.extend.exp, function (propertyName) {
                  return a.instance.getValue(propertyName, a.runtime);
                })
                return CtlUtils.expression(cellData, exp, a.runtime);
              case 12:
                if (data._summary)
                  return '';
                // 链接
                var cmd = cellInfo.extend.cmd || '',
                  content = '',
                  title = '';
                if (cellInfo.extend.icon) {
                  if (cellInfo.extend.icon.name)
                    content += '<i class="fa-fw ' + $F.fas(cellInfo.extend.icon.name) + '"></i>';
                  if (cellInfo.extend.icon.label) {
                    if (cellInfo.extend.icon.label.startWith('\\[tip\\]'))
                      title = cellInfo.extend.icon.label.substring(5);
                    else
                      content += '<span>' + Helper.filterXSS(cellInfo.extend.icon.label) + '</span>';
                  }
                } else
                  content = Helper.filterXSS(cellData + '');

                return content ? {
                  html: '<a href="javascript:;" class="dyn_link wisgrid_clickable" data-clickevent="' + column.name + '" data-cmd="' + cmd + '" title="' + title + '"a>' + content + '</a>'
                } : '';
              case 13:
                if (data._summary)
                  return '';
                // 动态输入
                if (cellInfo.extend.v) {
                  var editType = data[cellInfo.extend.v - 1];
                  if (editType == 5 || editType == 6) {
                    if (cellInfo.extend.v2) {
                      var relSql = data[cellInfo.extend.v2 - 1] || '';
                      if (relSql) {
                        a.doPubSql(relSql.trim(), editType == 6);
                        translateKey = 'P' + relSql;
                      } else {
                        $F.err('Ctls - Table', '下拉单元格未指定全局SQL, col=' + cellInfo.extend.v2);
                      }
                    }
                  }
                } // end of cellInfo.extend.v
                break;
              case 14:
                if (data._summary)
                  return '';
                // 动态扩展列，内容为扩展字段绑定的交叉表第一行
                var rs = a.wisGrid.getData();
                if (rs.length > 0) {
                  for (var i = 0; i < rs[0].length; i++) {
                    if (rs[0][i] == column.crossCol) {
                      column.realCol = i;
                      cellData = data[column.realCol];
                      //console.log('找到匹配的交叉列： ' + column.crossCol + ', col=' + i, cellData)
                      break;
                    }
                  }
                  if (column.realCol > -1) {
                    if (column.editType == 5 || column.editType == 6) {
                      var relSql = column.selectCol || '';
                      if (relSql) {
                        a.doPubSql(relSql.trim(), column.editType == 6);
                        translateKey = 'P' + relSql;
                      }
                    }
                  } else {
                    if (rs.length > 0) {
                      if (rs[0].length == 0) {
                        // 交叉表无数据，造成0行字段没有静态列
                        rs[0][a.staticCol + 1] = column.crossCol;
                        column.realCol = a.staticCol + 1;
                      } else {
                        rs[0].push(column.crossCol);
                        column.realCol = rs[0].length - 1;
                      }
                      //console.log('交叉列取值匹配失败： ' + column.crossCol + ', 创建列=' + column.realCol)
                    }
                  }
                }
                if (cellData == undefined) {
                  if (column.editType == 1)
                    cellData = data[column.realCol] = 0;
                  else
                    cellData = data[column.realCol] = '';
                }
            }

            // 对需要翻译的字段进行翻译
            // console.log('Translate of ' + translateKey);
            const tra = a.translater[translateKey];
            if (tra) {
              const old = cellData;
              if (a.multiEntity && a.multiEntity.contains(translateKey)) {
                // 下拉多选值
                if ($.type(cellData) == 'string') {
                  var cs = [];
                  $.each(cellData.split(','), function (i, v) {
                    if (tra[v] !== undefined)
                      cs.push(tra[v]);
                  })
                  cellData = cs.join(',')
                } else
                  cellData = tra[cellData];
              } else
                cellData = tra[cellData];
              // console.log('[' + column.realCol + ']', old, '->', cellData);
            }

            if (cellData === undefined || cellData === null)
              cellData = '';
            if ($.type(cellData) == 'object') {
              return cellData;
            } else {
              let exp = $F.ognl(cellInfo.e, function (propertyName) {
                return a.instance.getValue(propertyName, a.runtime);
              })
              return CtlUtils.expression(cellData, exp, a.runtime);
            }
          },
          editors: function (data, row, col) {
            var column = a.columns[col];
            if (!column.editable)
              return;

            // console.log(column);
            var cells = profile.props.local.cells,
              cellInfo = cells[column.defCol],
              cellData,
              editcmd = localPro(a, 'editcmd');
            //console.log(cellInfo)
            if (!data) {
              cellData = '';
            } else {
              if (data.length > column.realCol)
                cellData = data[column.realCol];
              else {
                // 在动态交叉列中，可能由于数据缺失造成数据实际长度小于定义的长度
                if (column.edittype == 2)
                  cellData = 0;
                else
                  cellData = '';
              }
            }

            var editType = column.editable;
            if (editType == 13 && data) {
              // 动态编辑类型
              var cellType = data[cellInfo.extend.v - 1];
              switch (cellType) {
                case 0: // 文本输入
                  editType = 1;
                  break;
                case 1: // 数值输入
                  editType = 2;
                  break;
                case 2: // 日期
                  editType = 3;
                  break;
                case 3: // 时间
                  editType = 4;
                  break;
                case 4: // 日期加时间
                  editType = 5;
                  break;
                case 5: // 下拉单选
                  editType = 21;
                  break;
                case 6: // 下拉多选
                  editType = 22;
                  break;
                default:
                  editType = 0;
              }
            } else if (editType == 14) {
              // 扩展列编辑类型
              switch (column.editType) {
                case 0: // 文本输入
                  editType = 1;
                  break;
                case 1: // 数值输入
                  editType = 2;
                  break;
                case 2: // 日期
                  editType = 3;
                  break;
                case 3: // 时间
                  editType = 4;
                  break;
                case 4: // 日期加时间
                  editType = 5;
                  break;
                case 5: // 下拉单选
                  editType = 211;
                  break;
                case 6: // 下拉多选
                  editType = 221;
                  break;
                default:
                  editType = 0;
              }
            }

            if (a.editorsCallback && !a.editorsCallback(row, column, cellInfo, data))
              return;

            switch (editType) {
              case 1: // 文本输入
              case 2: // 数字输入
              case 3: // 日期输入
              case 4: // 时间输入
              case 5: // 日期时间输入
                var input = $('<input type="text" class="dyn_input_inner" />').val(cellData);
                switch (editType) {
                  case 2:
                    Helper.makeNumberInput(input, true);
                    break;
                  case 3:
                    // 日期选择
                    input.datetimepicker({
                      fixed: true,
                      format: "Y-m-d",
                      timepicker: false
                    });
                    break;
                  case 4:
                    // 时间选择
                    input.datetimepicker({
                      fixed: true,
                      format: "H:i:s",
                      datepicker: false
                    });
                    break;
                  case 5:
                    // 日期时间选择
                    input.datetimepicker({
                      fixed: true,
                      format: "Y-m-d H:i:s"
                    });
                    break;
                }
                input.change(function (e) {
                  if (!data) {
                    // 新增数据行
                    // console.log('Append table [' + row + ':' + col + '] to ' + input.val() + ", real cell[" + row + ":" + column.realCol + "]");
                    data = [];
                    var newValue = input.val();
                    if (editType == 2)
                      newValue = parseFloat(newValue);
                    data[column.realCol] = newValue;
                    var R = {
                      DATA: data
                    };
                    doStructCmd(a, a.newcmd, R);
                    data = R.DATA;
                    a.wisGrid.getData().push(data);
                    a.wisGrid.appendEmpty();
                    a.wisGrid.refresh(row);
                    var cd = {};
                    cd[row] = data;
                    if (a.dataProp) {
                      a.instance.fireValue(a.dataProp, a.runtime, {
                        type: 1,
                        data: cd
                      }, a);
                    }
                  } else {
                    // console.log('Update table [' + row + ':' + col + '] to ' + input.val() + ", real cell[" + row + ":" + column.realCol + "]");
                    var newValue = input.val();
                    if (editType == 2)
                      newValue = parseFloat(newValue || '0');
                    var cd = {};
                    cd[row + ',' + column.realCol] = newValue;
                    if (a.dataProp) {
                      a.instance.fireValue(a.dataProp, a.runtime, {
                        type: 1,
                        data: cd
                      }, a);
                    } else {
                      data[column.realCol] = input.val();
                    }
                    a.wisGrid.refresh(row, col);
                  }

                  // 设置行记录修改标志
                  if (data['flag'] !== 2)
                    data['flag'] = 1;

                  // 定义了命令组，需要触发
                  if (editcmd) {
                    var R = {
                      GRIDROW: row + 1,
                      GRIDCOL: column.realCol + 1,
                      ROWDATA: data,
                      OLDVALUE: cellData
                    };
                    doStructCmd(a, editcmd, R);
                    if (!R['REFRESH'])
                      return;
                    else if (R['REFRESH'] == 1) {
                      a.wisGrid.refresh(row);
                    } else if (R['REFRESH'] == 2) {
                      a.wisGrid.refresh();
                    }
                  }
                }).blur(function (e) {
                  if (editType > 2) {
                    // datetimepicker
                    input.trigger('change');
                    input.datetimepicker('destroy')
                  }
                })
                return input;
              case 11: // 下拉选项
                var makeChange = function (value, label) {
                    if (!data) {
                      // 新增数据行
                      // console.log('Append table [' + row + ':' + col + '] to ' + value + ", real cell[" + row + ":" + column.realCol + "]");
                      data = [];
                      if (cellInfo.extend.v) {
                        data[column.realCol] = label;
                        data[cellInfo.extend.v - 1] = value;
                      } else
                        data[column.realCol] = value;
                      var R = {
                        DATA: data
                      };
                      doStructCmd(a, a.newcmd, R);
                      data = R.DATA;
                      a.wisGrid.getData().push(data);
                      a.wisGrid.appendEmpty();
                      a.wisGrid.refresh(row);
                      var cd = {};
                      cd[row] = data;
                      if (a.dataProp) {
                        a.instance.fireValue(a.dataProp, a.runtime, {
                          type: 1,
                          data: cd
                        }, a);
                      }
                    } else {
                      // console.log('Update table [' + row + ':' + col + '] to ' + value + ", real cell[" + row + ":" + column.realCol + "]");
                      var cd = {};
                      if (cellInfo.extend.v) {
                        // 更新值指定列
                        cd[row + ',' + column.realCol] = label;
                        cd[row + ',' + (cellInfo.extend.v - 1)] = value;
                        data[column.realCol] = label;
                        data[cellInfo.extend.v - 1] = value;
                        console.log(data)
                        a.wisGrid.refresh(row);
                      } else {
                        cd[row + ',' + column.realCol] = value;
                        data[column.realCol] = value;
                      }
                      if (a.dataProp) {
                        a.instance.fireValue(a.dataProp, runtime, {
                          type: 1,
                          data: cd
                        }, a);
                      }
                      a.wisGrid.refresh(row, col);
                    }

                    // 设置行记录修改标志
                    if (data['flag'] !== 2)
                      data['flag'] = 1;

                    // 定义了命令组，需要触发
                    if (editcmd) {
                      //console.log('trigger update command')
                      var R = {
                        GRIDROW: row + 1,
                        GRIDCOL: (cellInfo.extend.v ? cellInfo.extend.v : (column.realCol + 1)),
                        ROWDATA: data,
                        OLDVALUE: cellData
                      };
                      doStructCmd(a, editcmd, R);
                      if (!R['REFRESH'])
                        return;
                      else if (R['REFRESH'] == 1) {
                        a.wisGrid.refresh(row);
                      } else if (R['REFRESH'] == 2) {
                        a.wisGrid.refresh();
                      }
                    }
                  },
                  input;

                if (cellInfo.extend.v && data) // 值为其他列，本列仅作为标签显示
                  cellData = data[cellInfo.extend.v - 1];
                if (cellInfo.extend.ms || cellInfo.extend.fa) {
                  // 支持多选
                  input = $('<div style="width:100%"/>').wisCombox({
                    lang: $E.lang,
                    multi: cellInfo.extend.ms,
                    inputable: cellInfo.extend.fa,
                    onSelected: function (data) {
                      if (!data)
                        makeChange('', '');
                      else {
                        if (data instanceof Array) {
                          var values = [], labels = [];
                          $.each(data, function (i, v) {
                            labels.push(v.label);
                            values.push(v.value)
                          })
                          makeChange(values.join(','), labels.join(','))
                        } else {
                          makeChange(data.value, data.label);
                        }
                      }
                    }
                  })
                } else {
                  input = $("<select class='dyn_input_inner' />").on('change', function (e) {
                    makeChange(input.val(), $("option:checked", input).text())
                  });
                  input.on('keydown', function (e) {
                    if (e.keyCode == 46 || e.keyCode == 8) {
                      input.val('').trigger('change')
                    }
                  })
                }

                var extend = cellInfo.extend;
                if (!extend.source && extend.sql) {
                  // 兼容旧版模式
                  extend.source = {
                    type: 1,
                    options: {
                      sql: extend.sql,
                      binds: extend.binds,
                      cache: extend.cache,
                      key: extend.vf,
                      value: extend.lf
                    }
                  }
                }
                var dataSource = cellInfo.extend.source,
                  options = dataSource ? (dataSource.options || {}) : {},
                  isWisdgeCtrl = cellInfo.extend.ms || cellInfo.extend.fa;
                if (!dataSource.type) {
                  // 处理预加载数据选项
                  var cs = [];
                  for (var key in options) {
                    cs.push({
                      label: i18nResource(a, options[key]),
                      value: key
                    })
                  }
                  a._makeComboxEditor(input, cs, cellData, isWisdgeCtrl)
                } else if (dataSource.type == 1) {
                  // 处理动态加载数据选项
                  if (options.binds && options.binds.length) {
                    let params = {};
                    let needTranslate = extend.v ? false : true
                    // console.log(needTranslate)
                    $.each(options.binds, function (i, bind) {
                      if (bind[0] == '@') {
                        needTranslate = false
                        bind = parseInt(bind.substring(1)) - 1;
                        params['C' + (i + 1)] = data[bind];
                      } else
                        params['C' + (i + 1)] = a.instance.getValue(bind, a.runtime);
                    })
                    a.runtime.sqlQueue[options.sql] = {
                      params: params,
                      callback: function (data) {
                        var paramsStr = $F.display(params);
                        if (data.count < 0) {
                          $F.err('SQLExec - failed', 'DYN[' + a.runtime.struct.id + '] KEY[' + options.sql + '], 控件[' + a.type + '@' + a.path + '][PARAMS: ' + paramsStr + ']');
                          data = $.extend({
                            columns: [],
                            fields: []
                          }, data);
                        }
                        var sqlData = getDataFromSql(data, options.key, options.value);
                        a._makeComboxEditor(input, sqlData, cellData, isWisdgeCtrl);
                        if (needTranslate) {
                          let translationEntity = {};
                          $.each(sqlData, function (i, v) {
                            translationEntity[v.value] = v.label;
                          })
                          a.translater['T' + column.defCol] = translationEntity;
                        }
                        // console.log('Update translation T' + column.defCol);
                      }
                    }
                    a.instance.sqlexec(a.runtime);
                  } else {
                    var sqlData, cacheKey = options.cache ? (a.runtime.struct.id + '.' + options.cache) : '';
                    if (cacheKey) {
                      // 配置了缓存的数据集，从缓存取
                      sqlData = a.project.cache[cacheKey];
                      if (!sqlData)
                        console.error('未获取到缓存：' + cacheKey, a.project.cache);
                    } else {
                      // 未配置缓存的数据集，从translater中取
                      sqlData = a.translater['TMP#' + column.defCol];
                      if (!sqlData)
                        console.error('未获取到缓存：' + 'TMP#' + column.defCol, a.translater);
                    }
                    sqlData = getDataFromSql(sqlData, options.key, options.value);
                    a._makeComboxEditor(input, sqlData, cellData, isWisdgeCtrl);
                  }
                } else if (dataSource.type == 2) {
                  // 处理全局SQL数据项
                  var jsonSql = $F.ognl(options.sql, function (propertyName) {
                    if (propertyName.startWith('@')) {
                      return data[parseInt(propertyName.substring(1)) - 1];
                    } else {
                      return getValue(a.instance, propertyName, a.runtime);
                    }
                  })
                  var sqlData = a.project.queryCodetable(options.cache, jsonSql);
                  sqlData = getDataFromJsonSql(sqlData, options.key, options.value);
                  a._makeComboxEditor(input, sqlData, cellData, isWisdgeCtrl);
                } else if (dataSource.type == 3) {
                  // 处理绑定变量的数据源
                  if (options.n) {
                    var sqlData = a.instance.getValue(options.n, runtime);
                    if (sqlData) {
                      if (!options.t)
                        sqlData = getDataFromProperty(sqlData);
                      else
                        sqlData = getDataFromProperty(sqlData, options.v, options.l);
                      a._makeComboxEditor(input, sqlData, cellData, isWisdgeCtrl)
                    }
                  }
                }
                return isWisdgeCtrl ? input.combox : input;
              case 21: // 动态输入指定的下拉单选
              case 211: // 扩展列指定的下拉单选
              case 22: // 动态输入指定的下拉多选
              case 221: // 扩展列指定的下拉多选
                var makeChange = function (value, label) {
                    if (!data) {
                      // 新增数据行
                      // console.log('Append table [' + row + ':' + col + '] to ' + value + ", real cell[" + row + ":" + column.realCol + "]");
                      data = [];
                      data[column.realCol] = value;
                      var R = {
                        DATA: data
                      };
                      doStructCmd(a, a.newcmd, R)
                      data = R.DATA;
                      a.wisGrid.getData().push(data);
                      a.wisGrid.appendEmpty();
                      a.wisGrid.refresh(row);
                      var cd = {};
                      cd[row] = data;
                      if (a.dataProp) {
                        a.instance.fireValue(a.dataProp, a.runtime, {
                          type: 1,
                          data: cd
                        }, a);
                      }
                    } else {
                      // console.log('Update table [' + row + ':' + col + '] to ' + value + ", real cell[" + row + ":" + column.realCol + "]");
                      var cd = {};
                      cd[row + ',' + column.realCol] = value;
                      if (a.dataProp) {
                        a.instance.fireValue(a.dataProp, runtime, {
                          type: 1,
                          data: cd
                        }, a);
                      } else
                        data[column.realCol] = value;
                      a.wisGrid.refresh(row, col);
                    }

                    // 设置行记录修改标志
                    if (data['flag'] !== 2)
                      data['flag'] = 1;

                    // 定义了命令组，需要触发
                    if (editcmd) {
                      var R = {
                        GRIDROW: row + 1,
                        GRIDCOL: column.realCol + 1,
                        ROWDATA: data,
                        OLDVALUE: cellData
                      };
                      doStructCmd(a, editcmd, R);
                      if (!R['REFRESH'])
                        return;
                      else if (R['REFRESH'] == 1) {
                        a.wisGrid.refresh(row);
                      } else if (R['REFRESH'] == 2) {
                        a.wisGrid.refresh();
                      }
                    }
                  },
                  input;
                if (editType == 22 || editType == 221) {
                  input = $('<div style="width:100%"/>').wisCombox({
                    lang: $E.lang,
                    multi: true,
                    onSelected: function (data) {
                      if (!data)
                        makeChange('', '');
                      else {
                        var values = [], labels = [];
                        $.each(data, function (i, v) {
                          labels.push(v.label);
                          values.push(v.value)
                        })
                        makeChange(values.join(','), labels.join(','))
                      }
                    }
                  })
                } else {
                  input = $("<select class='dyn_input_inner' />").on('change', function (e) {
                    makeChange(input.val() || '', $("option:checked", input).text())
                  })
                  input.on('keydown', function (e) {
                    if (e.keyCode == 46 || e.keyCode == 8) {
                      input.val('').trigger('change')
                    }
                  })
                }

                var relSql;
                if ((editType == 21 || editType == 22) && cellInfo.extend.v2)
                  relSql = data[cellInfo.extend.v2 - 1] || '';
                else if (editType == 211 || editType == 221)
                  relSql = column.selectCol || '';

                if (relSql) {
                  var sqlData = a.doPubSql(relSql.trim(), editType == 22 || editType == 221);
                  if (sqlData) {
                    sqlData = getDataFromSql(sqlData);

                    if (editType == 21 || editType == 211) {
                      $.each(sqlData, function (i, v) {
                        input.append('<option value="' + v.value + '">' + v.label + '</option>');
                      })
                      input.val(cellData);
                    } else {
                      input.setData(sqlData);
                      input.val(cellData.split(','));
                    }
                  }
                }
                return input;
            }
          },
          visible: function (data) {
            // -99 为表头
            if (!data)
              return true;
            return (data['flag'] !== -99);
          },
          onSort: function (col, columnName, direct) {
            col = a.columns[col].realCol;

            var sortcmd = localPro(a, 'sortcmd');
            if (sortcmd) {
              doStructCmd(a, sortcmd, {
                COL: col + 1,
                COLNAME: columnName,
                DIRECT: direct
              });
            } else {
              $F.process(function () {
                var newData;
                if (direct == '')
                  newData = JSON.parse(JSON.stringify(a.originalValue))
                else newData = $.extend([], a.wisGrid.data.sort(function (a, b) {
                  var x, y;
                  if ($.type(a[col]) == 'string')
                    x = a[col].trim();
                  else
                    x = a[col];
                  if ($.type(b[col]) == 'string')
                    y = b[col].trim();
                  else
                    y = b[col];

                  if (x === y)
                    return 0;
                  if (x > y)
                    return (direct == 'asc') ? 1 : -1;
                  else
                    return (direct == 'asc') ? -1 : 1;
                }));
                // 将选择结果通知其他控件监听者
                if (a.dataProp)
                  a.instance.fireValue(a.dataProp, runtime, {
                    type: 0,
                    data: newData,
                    sort: true
                  });
              }, 'Sorting...')
            }
          },
          onPersonalize: function (data) {
            // console.log(data);
            if (data.type == 'orders' || data.type == 'hidden') {
              localStorage.setItem(a.runtime.struct.id + '.' + a.id, JSON.stringify(data.value));
              // CmdUtils.setLocalValue(a.runtime, a.id + '.orders', JSON.stringify(data.value))
            }
          },
          onDblClick: function (data, row, e) {
            $F.process(function () {
              $.each(a.dblclickCalls, function () {
                this(e, {
                  GRIDROW: row + 1,
                  ROWDATA: data
                });
              })
            })
          },
          onSelected: function (data, row, e) {
            selectCallback(data, row, e);
          },
          onUnselected: function () {
            selectCallback([], -1);
          },
          onMove: function (cur, tar, data) {
            const cs = {};
            cs[cur] = data[tar];
            cs[tar] = data[cur];
            a.instance.fireValue(a.dataProp, a.runtime, {
              type: 1,
              data: cs
            }, a);
            if (a.movecmd) {
              doStructCmd(a, a.movecmd, {
                CURDATA: cs[tar],
                TARDATA: cs[cur]
              })
              return true;
            }
          },
          onDrag: function (data) {
            if (a.dragcmd) {
              a.instance.fireValue(a.dataProp, a.runtime, {
                type: 0,
                data: data
              }, a);
              doStructCmd(a, a.dragcmd, {
                DATA: data
              })
              return true;
            }
          }
        };
        if (a.cellcmd) {
          a.options.onCellClick = function (data, row, col, e) {
            doStructCmd(a, a.cellcmd, {
              ROWDATA: data,
              GRIDROW: row,
              GRIDCOL: col,
              event: e
            })
          }
        }
        if (a.rowcmd || a.rowColorCell) {
          a.options.onCreateRow = function (tr, row, data) {
            if (a.rowColorCell !== undefined) {
              var cellData = data[a.rowColorCell - 1];
              if (cellData) {
                var cs = cellData.split('||');
                if (cs[0])
                  tr.css({
                    background: cs[0]
                  });
                if (cs.length > 1) {
                  tr.css({
                    color: cs[1]
                  })
                }
              }
            }
            if (a.rowcmd) {
              doStructCmd(a, a.rowcmd, {
                ROWDATA: data,
                GRIDROW: row,
                DOM: tr,
              })
            }
          }
        }
        if (a.filtecmd) {
          a.options.onFilter = function (filterArray) {
            doStructCmd(a, a.filtecmd, filterArray);
          }
        }
        a.wisGrid = a.input = $('<table />').appendTo(a.box).wisGrid(a.options).setData([]);

        const appendRow = function (newdata) {
          let data = a.wisGrid.getData();
          // why do this?
          //if (data.length === 0) {
          // var head = [];
          // head.flag = -99;
          // data.push(head);
          //}
          newdata = newdata || [];
          newdata['flag'] = 2; // 0: normal, 1: updated, 2:created
          // data.push(newdata);
          a.wisGrid.addData(newdata);
          if (a.dataProp) {
            a.instance.fireValue(a.dataProp, runtime, {
              type: 0,
              data: data
            }, a);
          }
          // console.log(data);
        }

        const cmitems = {};
        if (!localPro(a, 'discopy')) {
          cmitems.copy = {
            name: $F.lang('ctls.table.copy'),
            icon: "copy"
          }
        }
        if (a.newcmd) {
          cmitems.copynew = {
            name: $F.lang('ctls.table.copyToNew'),
            icon: "add"
          }
        }
        if (localPro(a, 'exportAble')) {
          cmitems.sep = '---------';
          cmitems.exportExcel = {
            name: $F.lang('ctls.table.exportExcel'),
            icon: 'file-export'
          }
        }
        if (localPro(a, 'exportExcel' )) {
          //拼接导出按钮
          let $button = $('<a href="javascript:;" title="导出"><i class="fa fa-cloud-download " style="margin: 0 5px;font-size: medium" aria-hidden="true" /></i></a>').on('click',function() {
            a.exportDataExcel('Untitled')
          });
          let $title =$('<div class="title-header" style="width: 100%;display: flex;justify-content: end; padding: 3px" />')
          $title.append($button)
          $title.insertBefore($('.wisgrid', a.dom))
        }
        // console.log(cmitems)
        if (Object.keys(cmitems).length > 0) {
          $.contextMenu({
            selector: ('#' + uniqueId + ' tr.wisgrid_row>td'),
            iconStyle: 'fa',
            onShow: function (e) {
              $(e).click();
            },
            callback: function (key, options, target) {
              if (key == 'copy') {
                var row = target.parent().attr('data-row'),
                  data = a.wisGrid.getData(),
                  rowdata = data[row];
                if (rowdata) {
                  H5Utils.clip(rowdata, function () {
                    $F.info($F.lang('sys.clipSuccess'))
                  })
                }
              } else if (key == 'copynew') {
                var row = target.parent().attr('data-row');
                var data = a.wisGrid.getData(),
                  rowdata = data[row];
                if (rowdata) {
                  appendRow($.extend(true, [], rowdata));
                }
              } else if (key == 'exportExcel') {
                a.exportExcel('Untitled',a.wisGrid.getData());
              }
            },
            items: cmitems,
            appendTo: container
          })
        }
      }
      a.dom.append('<div class="eliteDisabledMasker"/>');

      makeValueAndState(a);
    }

    /**
     * 收集表头信息
     */
    makeColumnsInfo(perz) {
      let a = this, localProps = a.profile.props.local;
      if (!localProps || !localProps.cells)
        return;

      a.columns.length = 0;
      var defaultResizable = localPro(a, 'resizable');
      // 创建一个新的sql执行队列
      var tmpRuntime = {
          struct: a.runtime.struct,
          sqlQueue: {}
        },
        totalWidth = 0;
      $.each(localProps.cells, function (col, cell) {
        if (cell.t == 14) {
          // 动态扩展列
          const params = {};
          $.each(cell.extend.binds, function (i, bind) {
            params['C' + (i + 1)] = a.instance.getValue(bind, a.runtime);
          })
          tmpRuntime.sqlQueue[cell.extend.sql] = {
            params: params,
            callback: function (data) {
              var paramsStr = $F.display(params);
              if (data.count < 0) {
                $F.err('SQLExec - failed', 'DYN[' + a.runtime.struct.id + '] KEY[' + cell.extend.sql + '], 控件[' + a.type + '@' + a.path + '][PARAMS: ' + paramsStr + ']');
                data = $.extend({
                  columns: [],
                  fields: []
                }, data);
              } else {
                // if (paramsStr.length > 2)
                if ($E.debug)
                  console.log('SQLExec - success', 'DYN[' + a.runtime.struct.id + '] KEY[' + cell.extend.sql + '], 控件[' + a.type + '@' + a.path + '][PARAMS: ' + paramsStr + ']');
              }
              $.each(data.fields, function (i, v) {
                var column = {
                  display: v[1],
                  name: 'th_' + col + '_' + i,
                  width: cell.w || 0,
                  align: ['left', 'center', 'right'][cell.a || 0],
                  multiline: localPro(a, 'ah', false),
                  flex: cell.x,
                  sortable: false,
                  editable: cell.t,
                  resizable: cell.r,
                  crossCol: v[0],
                  editType: (v.length > 2 ? v[2] : 0),
                  selectCol: (v.length > 3 ? v[3] : ''),
                  defCol: col,
                  realCol: -1
                }
                a.columns.push(column);
                if (cell.t)
                  a.editable = true;
              })
            }
          }
          a.crossDataFlag = 1;
          return true;
        }

        var dsql = function (col) {
          // 翻译列 与 下拉选择列
          var cacheKey, sqlId, binds;
          if (cell.t == 9) {
            sqlId = cell.extend.sql;
            cacheKey = cell.extend.cache;
            binds = cell.extend.binds;
          } else {
            sqlId = cell.extend.source.options.sql;
            cacheKey = cell.extend.source.options.cache;
            binds = cell.extend.source.options.binds;
          }
          if (cacheKey)
            cacheKey = a.runtime.struct.id + '.' + cacheKey;
          if (cacheKey && a.project.cache[cacheKey]) {
            if (cell.t == 11 && cell.extend.v) {
              // combox编辑，且值为指定列（其他列）
            } else {
              var translationEntity = {};
              $.each(a.project.cache[cacheKey].fields, function (i2, rs) {
                translationEntity[rs[0]] = rs[1];
              })
              a.translater['T' + col] = translationEntity;
              if (cell.t == 11 && cell.extend.ms) {
                a.multiEntity = a.multiEntity || [];
                a.multiEntity.push('T' + col);
              }
            }
          } else {
            // 变量传参遍历
            const exeSql = function (params, col, alone) {
              tmpRuntime.sqlQueue[sqlId] = {
                params: params,
                callback: function (data) {
                  if (data.count < 0) {
                    $F.err('SQLExec - failed', 'DYN2[' + a.runtime.struct.id + '] KEY[' + sqlId + '], 控件[' + a.path + '][PARAMS: {}]', data);
                    data = $.extend({
                      columns: [],
                      fields: []
                    }, data);
                  }
                  if (cacheKey) {
                    $F.log('Ctls - exeSql', '置入SQL结果缓存: ' + cacheKey)
                    a.project.cache[cacheKey] = data;
                  } else {
                    a.translater['TMP#' + col] = data;
                  }

                  // 下拉选项列如果指定了其他列为值，同样不需要翻译
                  if (cell.t == 11 && cell.extend.v)
                    return;

                  var translationEntity = {};
                  $.each(data.fields, function (i2, rs) {
                    translationEntity[rs[0]] = rs[1];
                  });
                  a.translater['T' + col] = translationEntity;

                  if (cell.t == 11 && cell.extend.ms) {
                    a.multiEntity = a.multiEntity || [];
                    a.multiEntity.push('T' + col);
                  }
                  a.refreshCol(col)
                }
              }
              if (alone)
                a.instance.sqlexec(tmpRuntime);
            }

            try {
              const params = {};
              if (binds) {
                $.each(binds, function (i, v) {
                  if (v.startWith('@')) throw new Error(v);
                  params['C' + (i + 1)] = a.instance.getValue(v, a.runtime);
                  a.instance.bindValue(v, a, function (e) {
                    params['C' + (i + 1)] = e.data;
                    exeSql(params, col, true);
                  });
                });
              }
              exeSql(params, col);
            } catch (e) {
            }
          }
        }
        if (cell.t == 11) {
          // 兼容旧版本
          if (!cell.extend.source && cell.extend.sql) {
            cell.extend.source = {
              type: 1,
              options: {
                cache: cell.extend.cache,
                sql: cell.extend.sql,
                binds: cell.extend.binds,
                key: cell.extend.vf,
                value: cell.extend.lf
              }
            }
          }
        }
        if (cell.t == 8) {
          a.rowColorCell = cell.o;
        } else if (cell.t == 9) {
          dsql(col)
        } else if (cell.t == 11) {
          var type = $F.getProperty(cell, 'extend.source.type');
          if (!type) {
            if (!cell.extend.v) {
              // 处理下拉编辑中的预加载数据选项
              var translationEntity = {}, options = cell.extend.source ? (cell.extend.source.options || {}) : {};
              for (var key in options) {
                translationEntity[key] = i18nResource(a, options[key]);
              }
              a.translater['T' + col] = translationEntity;
            }
          } else if (type == 1) {
            // 动态sql（无绑定变量）
            // const ref = cell.extend.source.options.binds.find((bind) => {
            //   return bind.startWith('@');
            // })
            // if (!ref)
            if (!cell.extend.source.options.binds.length)
              dsql(col);
          } else if (type == 2 || type == 3) {
            // 下拉选项列如果指定了其他列为值，同样不需要翻译
            if (!cell.extend.v) {
              var translationEntity = {},
                options = cell.extend.source ? (cell.extend.source.options || {}) : {},
                makeEntity = (sqlData) => {
                  $.each(sqlData, function (o, t) {
                    translationEntity[t.value] = t.label;
                  });
                  a.translater['T' + col] = translationEntity;
                  if (cell.extend.ms) {
                    a.multiEntity = a.multiEntity || [];
                    a.multiEntity.push('T' + col);
                  }
                };
              if (type == 2) {
                // 处理全局SQL数据项
                var jsonSql = $F.ognl(options.sql, function (propertyName) {
                  if (propertyName.startWith('@')) {
                    return data[parseInt(propertyName.substring(1)) - 1];
                  } else {
                    return getValue(a.instance, propertyName, a.runtime);
                  }
                })
                let sqlData = a.project.queryCodetable(options.cache, jsonSql);
                if (sqlData) {
                  sqlData = getDataFromJsonSql(sqlData, options.key, options.value);
                  makeEntity(sqlData);
                }
              } else {
                // 处理绑定变量的数据源
                if (options.n) {
                  a.instance.bindValue(options.n, a, function (e) {
                    let sqlData = e.data;
                    if (sqlData) {
                      if (!options.t)
                        sqlData = getDataFromProperty(sqlData);
                      else
                        sqlData = getDataFromProperty(sqlData, options.v, options.l);
                      makeEntity(sqlData);
                      a.refreshCol(col);
                    }
                  }, true);
                }
              }
            }
          }
        }

        var column = {
          display: i18nResource(a, cell.n),
          name: 'th_' + col,
          width: Math.max(cell.w || 0, 0),
          align: ['left', 'center', 'right'][cell.a || 0],
          sortable: cell.s,
          searchable: cell.f,
          filterable: cell.m,
          editable: cell.t,
          resizable: (defaultResizable || cell.r),
          wrapable: cell.p,
          defCol: col,
          realCol: (cell.o || 0) - 1,
          flex: cell.x,
          colName: (cell.t == 7 ? '多选' : (cell.t == 6 ? '单选' : ''))
        };
        if (cell.t)
          a.editable = true;
        a.staticCols = Math.max(column.realCol, a.staticCols)
        if (cell.c) {
          // 设置列颜色
          column.color = {
            background: cell.c.b,
            color: cell.c.c
          };
        }
        if (!column.width) {
          if (cell.t == 8)
            column.colorRender = true;
          column.forbidden = true;
        }
        totalWidth += column.width;
        a.columns.push(column);

        if (cell.t == 6) {
          // radio
          column.shift = true;	// define the column should not be able to print plan or export
          a.clickableEventCallbacks[column.name] = function (target, td, tr, e) {
            e.stopPropagation();
            var _r = parseInt(tr.attr('data-row')),
              _c = a.columns[parseInt(td.attr('data-col'))].realCol,
              cd = {};
            if (a.radios['C' + _c] == _r)
              return; // 防止重复点击

            cd[_r + ',' + _c] = 1;
            // 清除已有的单选选项
            if (a.radios['C' + _c] !== undefined)
              cd[a.radios['C' + _c] + ',' + _c] = 0;
            a.radios['C' + _c] = _r;
            a.instance.fireValue(a.dataProp, a.runtime, {
              type: 1,
              data: cd
            }, a);
          }
        } else if (cell.t == 7) {
          // checkbox
          column.shift = true;
          a.clickableEventCallbacks[column.name] = function (target, td, tr, e) {
            e.stopPropagation();
            $E.debug && console.log('checkbox cell event')
            const _r = parseInt(tr.attr('data-row')),
              _c = a.columns[parseInt(td.attr('data-col'))].realCol,
              cd = {},
              cmd = target.attr('data-cmd');
            cd[_r + ',' + _c] = (target.prop('checked') ? 1 : 0);
            if (a.dataProp) {
              a.instance.fireValue(a.dataProp, a.runtime, {
                type: 1,
                data: cd
              }, a);
            }

            doStructCmd(a, cmd, {
              GRIDROW: _r + 1,
              GRIDCOL: _c + 1,
              ROWDATA: a.value[_r],
              COLDATA: a.value[_r][_c]
            })
          }
          if (cell.n && cell.n[0] == '#') {
            column.display = cell.n.substring(1);
          } else {
            var ckb = $('<input type="checkbox" data-realcol="' + column.realCol + '" class="js_checkall" />');
            column.display = cell.n ? $('<label />').append(ckb).append(cell.n) : ckb;

            column.displayEvent = function () {
              ckb.off('click').on('click', function (e) {
                e.stopPropagation();
                let ckb = $(e.target),
                  c = parseInt(ckb.attr('data-realcol')),
                  state = ckb.prop('checked'),
                  row,
                  cd = {},
                  ckbName = a.path.replaceAll('\\.', '_') + '_ckb_' + c;
                $('input[type=checkbox][name="' + ckbName + '"]', a.wisGrid.table).each(function (i, item) {
                  item = $(item);
                  item.not(':disabled').prop('checked', state);
                  row = parseInt(item.closest('tr').attr('data-row'));
                  a.value[row][c] = cd[row + ',' + c] = state ? 1 : 0;
                });
                if (a.dataProp && Object.keys(cd).length > 0) {
                  a.instance.fireValue(a.dataProp, a.runtime, {
                    type: 1,
                    data: cd
                  }, a);
                }
                if (cell.extend && cell.extend.cmd) {
                  doStructCmd(a, cell.extend.cmd, {
                    CHECKALL: true,
                    GRIDCOL: c + 1,
                    STATE: state
                  })
                }
              })
            }
          }
        } else if (cell.t == 12) {
          // link
          a.clickableEventCallbacks[column.name] = function (target, td, tr, e) {
            e.preventDefault();
            $E.debug && console.log('link cell event')
            var _r = parseInt(tr.attr('data-row')),
              _c = a.columns[parseInt(td.attr('data-col'))].realCol,
              cd = {},
              cmd = target.attr('data-cmd');

            // console.log(_r, a.value)
            if (cmd) {
              $F.process(function () {
                doStructCmd(a, cmd, {
                  GRIDROW: _r + 1,
                  GRIDCOL: _c + 1,
                  ROWDATA: a.value[_r],
                  COLDATA: a.value[_r][_c],
                  DOM: target
                })
              })
            }
          }
        }
      });
      a.instance.sqlexec(tmpRuntime);

      // 获取本地个性化存储的数据
      if (a.percentSplit) {
        if (a.rmcmd)
          totalWidth += 27;
        if (a.vicmd)
          totalWidth += 27;
        if (!localPro(a, 'hideline', false))
          totalWidth += 28;
        $.each(a.columns, function (i, v) {
          if (v.width)
            v.pw = v.width * 100 / totalWidth;
        })
      } else {
        var orders = perz || localStorage.getItem(a.runtime.struct.id + '.' + a.id);
        if (orders) {
          if ($.type(orders) == 'string')
            orders = JSON.parse(orders);
          var getColumn = function (unit) {
            var c;
            for (var i = 0; i < a.columns.length; i++) {
              c = a.columns[i];
              if (c.name == unit.n) {
                if (!unit.v)
                  c.hide = true;
                a.columns.splice(i, 1);
                return c;
              }
            }
            return null;
          }
          let resort = [], one;
          $.each(orders, function (i, v) {
            one = getColumn(v);
            if (one)
              resort.push(one);
          })
          a.columns = resort.concat(a.columns);
        }
      }

      // 添加行删除按钮
      if (a.rmcmd) {
        a.columns.unshift({
          display: '',
          name: 'th_remove_flag',
          width: 26,
          align: 'center',
          sortable: false,
          flag: 2,
          shift: true
        });
        a.flagCols++;
      }
      // 添加行编辑按钮
      if (a.vicmd) {
        a.columns.unshift({
          display: '',
          name: 'th_edit_flag',
          width: 26,
          align: 'center',
          sortable: false,
          flag: 1,
          shift: true
        });
        a.flagCols++;
      }
    }

    doPubSql(relSql, multiEntity) {
      var a = this,
        sqlData = [];
      if (a.translater['P' + relSql]) {
        if (multiEntity) {
          a.multiEntity = a.multiEntity || [];
          if (!a.multiEntity.contains('P' + relSql)) {
            //console.log('Make translation as multi: P' + relSql, a.multiEntity)
            a.multiEntity.push('P' + relSql);
          }
        }
        return a.runtime.project.cache[a.runtime.struct.id + ".PS#" + relSql];
      } else {
        $F.dataService('JSGenerateServlet4', {
          data: {
            token: a.project.token,
            dbPool: "",
            name: "Dyn.PublicSql.Get",
            pubKey: relSql
          },
          async: false,
          digest: a.project.digest,
          success: function (data) {
            if (data.code > 0 && data.value.count >= 0) {
              sqlData = a.runtime.project.cache[a.runtime.struct.id + ".PS#" + relSql] = data.value;
              var translationBean = {};
              $.each(data.value.fields, function (i, v) {
                translationBean[v[0]] = v[1];
              });
              a.translater['P' + relSql] = translationBean;
              if (multiEntity) {
                a.multiEntity = a.multiEntity || [];
                if (!a.multiEntity.contains('P' + relSql)) {
                  // console.log('Make translation as multi: P' + relSql, a.multiEntity)
                  a.multiEntity.push('P' + relSql);
                }
              }
            } else {
              $F.err('Ctls - Table', '全局SQL[' + relSql + ']执行失败: ' + data.message);
            }
          }
        });
        return sqlData;
      }
    }

    _makeComboxEditor(combox, data, value, isWisdgeCtrl) {
      if (isWisdgeCtrl) {
        combox.setData(data);
        if (combox.options.multi) {
          if (value) {
            if ($.type(value) === 'string') {
              value = value.split(',');
            } else {
              value = [value]
            }
          }
        }
        combox.val(value);//.trigger('change');
      } else {
        $.each(data, function (i, v) {
          combox.append('<option value="' + v.value + '">' + v.label + '</option>');
        });
        combox.val(data.length ? value : '');//.trigger('change');
      }
    }

    val(t,sort = false) {
      var a = this
      if( !sort ) {
        a.originalValue = JSON.parse(JSON.stringify(t));
      }
      if (!arguments.length)
        return a.value || [];
      else {
        if (a.crossDataFlag) {
          // 标记了交叉表类型的数据，首行隐藏
          if (t.length > 0) t[0].flag = -99;
        } else if (localPro(a, 'fh')) {
          // 首行数据变表头
          if (t.length > 0) {
            $.each(a.wisGrid.options.columns, function (i, column) {
              if (column.flag)
                return;
              a.wisGrid.setColumnLabel(i, t[0][column.realCol]);
            })
            t.splice(0, 1);
          }
        }
        a.value = t;

        if (a.wisGrid) {
          a.radios = {};
          a.wisGrid.setData(a.value);
          // 刷新checkbox全选状态
          $('.js_checkall', a.wisGrid.hTable).prop('checked', false);
        }
        return a;
      }
    }

    evt(e) {
      var a = this;
      if (e == undefined) {
        return {
          type: 0,
          data: a.val()
        }
      } else {
        if (a.scrollThread)
          clearInterval(a.scrollThread);

        if (e.type) {
          if (a.wisGrid) {
            var array = [],
              ks,
              row;
            for (var key in e.data) {
              ks = key.split(',');
              if (ks.length == 1) {
                if (ks[0] == '-1') {
                  // 整表更新
                  a.wisGrid.setData(a.wisGrid.getData(), 2);
                } else {
                  // 整行更新
                  // a.wisGrid.getData()[parseInt(ks[0])] = e.data[key];
                  a.wisGrid.refresh(parseInt(ks[0]))
                }
              } else {
                // console.log('TABLE[' + a.id + '] refresh cell:' + ks[0] + ',' + ks[1] + ' = ' + e.data[key])
                var col = parseInt(ks[1]);
                $.each(a.columns, function (i, column) {
                  if (column.realCol == col) {
                    // console.log('detecting column:' + i, column)
                    a.wisGrid.refresh(parseInt(ks[0]), i);
                    return false;
                  }
                })
              }
            }
          }
          return a;
        } else
          return a.val(e.data, e.sort);
      }
    }

    append(e) {
      var a = this;
      a.wisGrid.addData(e);
      if (a.dataProp) {
        a.instance.fireValue(a.dataProp, a.runtime, {
          type: 0,
          data: a.wisGrid.getData()
        }, a)
      }
    }

    bind(e, callback) {
      if (e == 'dblclick')
        this.dblclickCalls.push(callback);
      else if (e == 'click')
        this.changeCalls.push(callback);
      else
        this.wisGrid.table.bind(e, callback);
    }

    trigger(e) {
      if (e == 'dblclick') {
        $.each(this.dblclickCalls, function () {
          this();
        })
      } else if (e == 'click') {
        $.each(this.changeCalls, function () {
          this();
        })
      } else {
        this.wisGrid.table.trigger(e);
      }
    }

    nextRow() {
      this.wisGrid.selectNext();
    }

    prevRow() {
      this.wisGrid.selectPrev();
    }

    jumpRow(row) {
      this.wisGrid.jumpRow(row - 1);
    }

    columnLabel(col, title) {
      this.wisGrid.setColumnLabel(col - 1 + this.flagCols, title);
    }

    /**
     * 设置列宽
     * @param col
     * @param width
     */
    cellWidth(col, width) {
      this.wisGrid.setCellWidth(col - 1 + this.flagCols, width);
    }

    /**
     * 重新排列表格的列顺序
     * @param orders
     */
    cellsOrder(orders) {
      this.wisGrid.setCellsOrder(orders);
    }

    sort(col, direct) {
      this.wisGrid.sortColumn(col - 1 + this.flagCols, direct);
    }

    refresh() {
      if (arguments.length > 1)
        this.wisGrid.refresh(arguments[0] - 1, arguments[1] - 1 + this.flagCols);
      else if (arguments.length < 1)
        this.wisGrid.refresh();
      else
        this.wisGrid.refresh(arguments[0] - 1);
    }

    refreshCol(c) {
      console.log('refresh column: ' + c, this.wisGrid)
      this.wisGrid && this.wisGrid.refreshCol(c);
    }

    /**
     * 为一个空白的表格控件创建UI
     * @param columns {array} 表头数组
     *
     *  column = {
     *		display: {string} 单元格标签，用于显示,
     *		name: {string} 单元格名称，保留属性，不重复即可,
     *		width: {integer} 单元格宽度，-1为该列不显示，0为自动宽度,
     *		align: {integer} 单元格对齐 0='left', 1='center', 2='right',
     *		sortable: {boolean} 单元格是否可排序
     *		color: {string} 单元格颜色，例如#000000
     * 	}
     *
     * @param modelsCallback {callback} 数据渲染回调方法, function(data, row, col)
     * @param selectedCallback {callback} 点选行后的回调方法, function(data, row)
     * @param dblclickCallback {callback} 双击行后的回调方法, function(data, row)
     * @param data {array} 表格数据（二维数组）
     */
    build(columns, modelsCallback, selectedCallback, dblclickCallback, data) {
      var a = this;
      if (a.wisGrid) {
        return $F.alert('当前表格控件对象已经被创建过UI，不可重复。');
      }

      a.wisGrid = $('<table />').appendTo(a.dom).wisGrid({
        columns: columns,
        models: modelsCallback,
        onSelected: selectedCallback,
        onDblClick: dblclickCallback
      }).setData(data);
    }

    /**
     * 向上滚动表格
     */
    scrollUp(second) {
      var a = this;
      if (a.scrollThread)
        clearInterval(a.scrollThread);
      if (second > 0) {
        a.scrollThread = setInterval(function () {
          //console.log('Ctls - table', '调度表格自动向上滚屏: ' + a.id)
          if (a.wisGrid.bDiv[0].scrollHeight > a.wisGrid.bDiv[0].clientHeight)
            a.wisGrid.scrollUp(500);
        }, second * 1000);
      }
    }

    /**
     * 向下滚动表格
     */
    scrollDown(second) {
      var a = this;
      if (a.scrollThread)
        clearInterval(a.scrollThread);
      if (second > 0) {
        a.scrollThread = setInterval(function () {
          // console.log('Ctls - table', '调度表格自动向下滚屏: ' + a.id + ' every ' + secends + ' \'s');
          if (a.wisGrid.bDiv[0].scrollHeight > a.wisGrid.bDiv[0].clientHeight)
            a.wisGrid.scrollDown(500);
        }, second * 1000);
      }
    }

    /**
     * 上移某行
     * @param row
     */
    rowMoveUp(row) {
      if (this.wisGrid) {
        if (row === undefined)
          row = this.wisGrid.getSelectedIndex();
        else
          row = parseInt(row) - 1;

        if (row < 1)
          return false;

        return this.wisGrid.moveUp(row);
      } else
        return false;
    }

    /**
     * 下移某行
     * @param row
     */
    rowMoveDown(row) {
      if (this.wisGrid) {
        if (row === undefined)
          row = this.wisGrid.getSelectedIndex();
        else
          row = parseInt(row) - 1;

        if (row + 1 >= this.wisGrid.getData().length)
          return false;

        return this.wisGrid.moveDown(row);
      } else
        return false;
    }

    driftLayer(layer) {
      if ($.type(layer) == 'string')
        layer = this.runtime.ctls[layer];
      var row = this.wisGrid.getSelectedDom();
      if (row.length == 0)
        return $F.err('Ctls - table', '当前没有选中的行'), 0;
      var owner = $('td.cur', row);
      if ($.isFunction(layer.drift)) {
        setTimeout(function () {
          layer.drift(owner.offset().top, owner.offset().left, owner.offset().top + owner.outerHeight(), owner.offset().left + owner.outerWidth());
        }, 10)
      } else
        $F.alert('控件不支持drift方法', layer)
    }

    redraw() {
      this.wisGrid.redraw();
    }

    hidden(columns) {
      this.wisGrid.hidden(columns);
    }

    bindProperty(propertyName) {
      // 绑定了变量的控件，值改变时触发消息通知给实例
      var a = this;
      propertyName = propertyName.toLowerCase();
      a.bind('change', function (e) {
        a.instance.fireValue(propertyName, a.runtime, a.evt(), a);
      });

      // 绑定值消息，当实例改变时，控件同步更新
      a.val(a.instance.getValue(propertyName, a.runtime));
      a.instance.bindValue(propertyName, a, function (e) {
        a.evt(e);
      });
    }

    exportExcel(title, data, selectColumns) {
      var a = this,
        excel = [],
        head = [],
        infos = [],
        columns = [];

      title = title || "Grid export";
      const lock = {
        colSplit: 0,
        rowSplit: 2,
        leftmostColumn: 0,
        topRow: 2,
      };

      const cells = selectColumns || a.wisGrid.options.columns;
      let cellInfo, first = true;
      $.each(cells, function (i, cell) {
        if (cell.shift || !cell.width) return;
        if (first) {
          head.push({
            value: title,
            height: 30,
            align: 2,
            colspan: cells.length,
            rowspan: 1,
            fontFamily: 'Microsoft Yahei',
            fontBold: true,
            fontSize: 18
          })
          first = false;
        } else {
          head.push({
            value: '',
            occupied: true,
            align: 2,
            colpos: 1,
            rowpos: i + 1
          })
        }
        columns.push(cell.display || '');
        // columns.push({
        // 	value : (cell.display || ''),
        // 	fontFamily : 'Microsoft Yahei',
        // 	fontBold : true,
        // 	fontSize : 10,
        // 	border : true,
        // 	bgColor : '#e7f5fb',
        // 	grid : true
        // })

        cellInfo = a.profile.props.local.cells[cell.defCol];
        infos.push({
          align: cellInfo.a || 0,
          width: cell.width,
          visible: true
        })
      })
      if (title)
        excel.push(head);
      else
        lock.topRow = 1;
      excel.push(columns);

      const wsData = [columns];
      $.each(data , function (i, data) {
        // -99 为表头
        if (!data || data['flag'] == -99)
          return;

        const cs = [];
        //cs是数据的集合
        cells.forEach((cell, k) => {
          if (cell.shift || !cell.width) return;
          cs.push(a.wisGrid.options.plains(data, i, k))
        })
        wsData.push(cs);
        // var cs = [],
        // 	bg,
        // 	fc,
        // 	cellInfo;
        // $.each(cells, function(k, cell) {
        // 	if (cell.shift)
        // 		return;
        //
        // 	cs.push({
        // 		value : a.wisGrid.options.plains(data, i, k), //getValue(k, cells[k], data[cell.o - 1]),
        // 		border : true,
        // 		grid : true
        // 	})
        // 	// 根据列值决定行颜色
        // 	cellInfo = a.profile.props.local.cells[cell.defCol];
        // 	if (cellInfo.t == 8 && data[cell.realCol]) {
        // 		//console.log('color: ' + data[cell.o - 1])
        // 		var colors = data[cell.realCol].split('||');
        // 		if (colors[0])
        // 			bg = colors[0]
        // 		if (colors.length > 1)
        // 			fc = colors[1]
        // 	}
        // })
        // if (bg || fc) {
        // 	$.each(cs, function(k, v) {
        // 		if (!v)
        // 			return;
        //
        // 		if (bg)
        // 			v.bgColor = bg;
        // 		if (fc)
        // 			v.color = fc;
        // 	})
        // }
        // excel.push(cs);
      })

      // const wsData = [];
      // excel.forEach( row => {
      // 	const rowData = [];
      // 	row.forEach( col => {
      // 		rowData.push(col.value)
      // 	})
      // 	wsData.push(rowData);
      // })
      // console.log(wsData);
      const wb = XLSX.utils.book_new();
      wb.Props = {
        Title: title,
        Author: "EliteCRM Grid Exporter",
        CreatedDate: new Date()
      };
      const ws = XLSX.utils.aoa_to_sheet(wsData);
      // wb.SheetNames.push('Sheet1');
      // wb.Sheets['Sheet1'] = ws;
      XLSX.utils.book_append_sheet(wb, ws, '第一页');
      require(['js/lib/filesaver.min'], function (saveAs) {
        XLSX.writeFile(wb, title + ".xlsx", {
          bookType: 'xlsx',
          type: 'binary',
          compression: true
        });
      })

      // wb.SheetNames.push(title);
      // wb.Sheets[title] = XLSX.utils.aoa_to_sheet(wsData);
      // const wbout = XLSX.write(wb, {
      // 	bookType:'xlsx',  type: 'binary'
      // });
      // function s2ab(s) {
      // 	var buf = new ArrayBuffer(s.length);
      // 	var view = new Uint8Array(buf);
      // 	for (var i=0; i<s.length; i++)
      // 		view[i] = s.charCodeAt(i) & 0xFF;
      // 	return buf;
      // }
      // require(['js/lib/filesaver.min'], function(saveAs) {
      // 	saveAs(new Blob([s2ab(wbout)],{type:"application/octet-stream"}), title + '.xlsx');
      // })
      // var busyId = $F.busyShow();
      // Ajax.post({
      // 	url: $E.url + '/reporter/export_',
      // 	contentType: 'json',
      // 	data: JSON.stringify({
      // 		token: a.instance.project.token,
      // 		digest: a.instance.project.digest,
      // 		dbPool: '',
      // 		id: Guid.raw(),
      // 		name: title,
      // 		data: excel,
      // 		infos: infos,
      // 		lock: lock,
      // 	}),
      // 	digest: a.instance.digest,
      // 	success: function(data) {
      // 		// console.log(data);
      // 		if (data.code > 0) {
      // 			var url = $E.url + '/fs/get?d=true&' + encodeURI('file=' + data.value + '&as=' + (title || 'export') + '.xlsx');
      // 			window.open(url, '_blank');
      // 		} else
      // 			$F.alert(data.message);
      // 	},
      // 	always: function() {
      // 		$F.busyHide(busyId);
      // 	}
      // })
    }
    exportDataExcel(title, data) {
      //弹窗
      var a = this;
      const exportRange = localPro(a, 'exportExcel')
      const exportCmd = localPro(a, 'exportCmd')
      let newCs = []
      var cs = a.wisGrid.options.columns;
      var dom = $('<div id="export-dialog" style="width:100%;height:100%; display: flex;flex-direction: column;justify-content: space-between"><div class="file-name-wrapper"></div><h3 style="margin: 6px 0">选择导出列</h3><div class="selected-columns"></div>');
      $('.file-name-wrapper', dom).append($('<div>文件名：<input class="file-name" type="text" id="myInput"></div>'))
      $F.createDialog(dom, {
        title: (exportRange == 1 ? '全部':'当前页') +'导出',
        resizable: {
          minWidth: 900,
          minHeight: 400
        },
        contentCSS: {
          padding: 8
        },
        width: '400px',
        height: '350px',
        onOpen: function () {
          let dialog = this;
          $('.selected-columns', dom).wisList({
            lang: $E.lang,
            multiSelect: 0,
            checkable: 1,
            actionBar: 1,
            labels: function (e) {
              return {
                dom: '<span class="selector_item_name">' + e.display + '</span>'
              }
            },
            values: function (e) {
              return e.realCol;
            },
            onChange: function (data) {
              newCs = data
            },
          }).setData(cs);
        },
        onSubmit: function (close) {
          //当前页
          let input = $('#myInput', dom)
          if(!input.val()){
            $F.alert('请输入文件名');
            return
          }
          if(exportRange == 2) a.exportExcel(input.val(), a.wisGrid.getData(), newCs);
          else if(exportRange == 1 && exportCmd) {
            //全部
            doStructCmd(a, exportCmd, {
              callback: function (sqlInfo) {
                let dslSqls = sqlInfo.sqls[0].dsls
                let dslSqlMap ={}
                let sqlParams = {}
                for (var prop in dslSqls) {
                  dslSqlMap[prop] =dslSqls[prop].map(item => {
                    sqlParams = {...item.params,...sqlParams}
                    return item.sqlId
                  } )
                }
                let params = {
                  headers: newCs.map(item =>{ return {title:item.display , ref:item.realCol}}),
                  sqlInfo: {
                    dynId:sqlInfo.dynId,
                    dbPool:sqlInfo.dbPool,
                    version:sqlInfo.version,
                    dbQueryInfo: {
                      sqlId: sqlInfo.sqls[0].sqlId,
                      dslSqlMap: dslSqlMap,
                      params: {...sqlParams,...(sqlInfo.sqls[0].params||{})}
                      // assign(sqlInfo.sqls[0].params,sqlParams) || {}
                    }
                  }
                }
                Ajax.post({
                  url: $E.url + '/reporter/tableDataExport',
                  data:JSON.stringify(params),
                  contentType : 'application/json;charset=UTF-8',
                  dataType: 'binary', // 将响应数据类型设置为二进制数据
                  success: function(res) {
                    // if(res.code == 1)
                    //   var blob = new Blob([res] );
                    require(['js/lib/filesaver.min'], function(saveAs) {
                      saveAs(new Blob([res],{type:"application/octet-stream"}), input.val() + '.xlsx');
                    })
                  },
                })
              },
              _data_export: true
            });
          }
          close();
        },
        onCancel:function (close) {
          $('#myInput',dom).val('')
          close();
        }
      });

    }
    destroy() {
      let a = this, uniqueId = a.dom.attr('id');
      $.contextMenu('destroy', '#' + uniqueId + ' tr.wisGrid_row>td');
      if (a.scrollThread)
        clearInterval(a.scrollThread);
      if (a.input)
        a.input.destroy();
      super.destroy();
    }

    /**
     * 将个性化表格列设置存入数据库
     * @param key {string} 保存于数据库中的主键名
     */
    savePerz(key) {
      var a = this,
        perz = localStorage.getItem(a.runtime.struct.id + '.' + a.id);

      var dataBound = new BoundData('TAB_OPTIONAL3' + $E.staff.epidSql);
      dataBound.setKey('STAFF_ID', $E.staff.id);
      dataBound.setKey('PATH', key);
      dataBound.setValue('CONTENT', perz);
      $F.dataService('SystemDo5', {
        data: {
          token: a.project.token,
          dbPool: '',
          xml: dataBound.getData()
        },
        digest: a.project.digest,
        async: false,
        success: function (data) {
          if (data.code > 0)
            $F.notify($F.lang('message.perzsaved'));
          else
            $F.notify(data.message, -1);
        }
      })
    }

    /**
     * 从数据库读出个性化表格列布局并重绘表格
     * @param key {string} 保存于数据库中的主键名
     */
    loadPerz(key) {
      var a = this;
      var perz = $F.query('Optional3.get', a.project.token, a.project.digest, [key], 0);
      //console.log(perz);
      a.setPerz(perz);
    }

    /**
     * 设置表格的自定义列布局
     * @param perzJson {string} 将一个json格式的表格自定义列布局对象应用到表格上
     */
    setPerz(perzJson) {
      var a = this;
      a.makeColumnsInfo(perz);
      a.wisGrid.options.columns = a.columns;
      a.wisGrid.redraw();
    }
  }

  /*
	 * 动态页面组件：Tab
	 */
  class CtlTab extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      a.ctls = [];
      //console.log(a);

      var skin = makeStyles(a),
        autoHeight = true,
        padding = skin.styles['padding'],
        border = skin.styles['border'] || false;
      delete skin.styles['border'];
      delete skin.styles['padding'];
      a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles)
      if (parseInt(a.dom.css('flex-grow'))) {
        // 用于处理弹性布局单元
        // 当控件处于弹性单元时，应该将对应的宽或者高的值去除
        if (a.dom.parent().css('flex-direction') == 'column')
          a.dom.css('height', 0);
        else
          a.dom.css('width', 0);

        //if (H5Utils.isChromeLegacy)
        a.box = $('<div class="wrapper" />').appendTo(a.dom);
        autoHeight = false;
      } else {
        var height = skin.styles['height'];
        if (height === undefined || height == '' || height == 'auto')
          autoHeight = true;
        else
          autoHeight = false;
      }
      var tabNav = $('<ul />').appendTo(a.box);
      var drawContent = function (sub, tabId, container) {
        if (!container)
          container = $('<div id="' + tabId + '" />').appendTo(a.box).data('_dynControl', a);

        container.data('draw', true);
        if (sub.type) {
          // 内容为组件
          $D.drawCtls([{
            id: a.id + '.' + sub.key,
            name: 'component',
            props: {
              styles: {
                width: '100%',
                height: (autoHeight ? 'auto' : '100%')
              },
              local: {
                com: sub.rel
              }
            }
          }], container, a.runtime, a.namespace + '.' + sub.key, data, rowNum);
          a.ctls = a.ctls.concat(container.data('controls'));
        } else {
          // 内容为子页面
          var page = $D.getPage(runtime, sub.rel);
          if (!page)
            $F.err('Ctls - Tab', '不存在的子页面：' + sub.rel);
          else {
            a.instance.beforeDraw(runtime, page);
            $D.drawCtls(page.ctls, container, a.runtime, a.namespace + '.' + sub.rel);
            a.ctls = a.ctls.concat(container.data('controls'));
            a.instance.afterDraw(runtime, page);
          }
        }
      };

      var cells = localPro(a, 'cells');
      if (cells.type == 1) {
        // 动态TAB控件
        if (cells.key !== undefined && cells.id !== undefined && cells.label !== undefined) {
          var getSub = function (bean) {
              for (var i = 0; i < cells.items.length; i++) {
                if (cells.items[i].key == bean[cells.key])
                  return cells.items[i];
              }
            },
            makeTabs = function (beans) {
              var tabId,
                li,
                link,
                sub,
                activeTab;
              $.each(beans, function (i, bean) {
                sub = getSub(bean);
                if (!sub) {
                  $F.err('Ctls - ctlTab', '不存在的面板类型：' + bean[cells.key], bean);
                } else {
                  tabId = a.id + '_' + sub.key;
                  li = $('<li data-index="' + i + '" data-ref="' + tabId + '" data-sub="' + bean[cells.id] + '" />').appendTo(tabNav).data('entity', sub);
                  link = $('<a href="javascript:;" data-ref="' + tabId + '" />').appendTo(li);
                  if (sub.visiable === 0) {
                    li.css('display','none')
                  }
                  if (sub.visiable === 2) {
                    activeTab = i
                  }
                  if (sub.icon)
                    link.append($F.parseIcon(sub.icon).addClass('pull-left'));
                  link.append('<span>' + i18nResource(a, bean[cells.label]) + '</span>');
                }
              })
              a.input = a.box.wisTab({
                activeTabId: activeTab,
                autoHeight: autoHeight,
                border: border,
                contentPadding: padding,
                vertical: localPro(a, 'direct'),
                onUnselected: function (id, container, tab, callback) {
                  var sub = tab.data('entity');
                  if (sub.cmd) {
                    var R = {
                      SUB: sub,
                      DATA: parseInt(tab.attr('data-index')),
                      ID: tab.attr('data-sub'),
                      ACTIVE: false,
                      process: callback
                    };
                    doStructCmd(a, sub.cmd, R);
                    if (!R.STOP)
                      callback();
                  } else
                    callback();
                },
                onSelected: function (id, container, tab) {
                  var sub = tab.data('entity');
                  if (!container.data('draw'))
                    drawContent(sub, id, container);
                  const event = {
                    SUB: sub,
                    DATA: parseInt(tab.attr('data-index')),
                    ID: tab.attr('data-sub'),
                    ACTIVE: true
                  }
                  if (a.changeCallback) a.changeCallback(event)
                  if (sub.cmd) {
                    doStructCmd(a, sub.cmd, event);
                  }
                }
              });
            }
          if (cells.os) {
            // 使用变量
            cells.key = parseInt(cells.key);
            cells.id = parseInt(cells.id);
            cells.label = parseInt(cells.label);

            if (cells.prop) {
              a.instance.bindValue(cells.prop, a, function (e) {
                a.input && a.input.destroy();
                makeTabs(e.data);
              }, true)
            }
          } else {
            // 使用SQL
            cells.key = cells.key.toUpperCase();
            cells.id = cells.id.toUpperCase();
            cells.label = cells.label.toUpperCase();

            var execute = function (sqlId, params) {
              var tmpRuntime = {
                struct: a.runtime.struct,
                sqlQueue: {}
              };
              tmpRuntime.sqlQueue[sqlId] = {
                params: params,
                callback: function (data) {
                  var paramsStr = $F.display(params);
                  if (data.count < 0) {
                    $F.err('SQLExec - failed', 'DYN[' + a.runtime.struct.id + '] KEY[' + sqlId + '], 控件[' + a.type + '@' + a.id + '][PARAMS: ' + paramsStr + ']');
                    data = $.extend({
                      columns: [],
                      fields: []
                    }, data);
                  } else {
                    if ($E.debug)
                      console.log('SQLExec - success', 'DYN[' + a.runtime.struct.id + '] KEY[' + sqlId + '], 控件[' + a.type + '@' + a.id + '][PARAMS: ' + paramsStr + ']');
                  }
                  var beans = $F.makeJsonArray(data);
                  //console.log(beans)
                  makeTabs(beans);
                }
              }
              a.instance.sqlexec(tmpRuntime);
            };

            // 动态SQL取值
            var params = {};
            cells.binds.forEach(function (v, i) {
              v = v.toLowerCase();
              if (v[0] == '@') {
                v = parseInt(v.substring(1)) - 1;
                params['C' + (i + 1)] = a.xData[v];
              } else {
                // 需要支持动态刷新
                params['C' + (i + 1)] = a.instance.getValue(v, runtime);
                a.instance.bindValue(v, a, function (e) {
                  params['C' + (i + 1)] = e.data;
                  // 创建回调选项方法
                  a.tabs.destroy();
                  execute(cells.sql, params);
                });
              }
            })
            execute(cells.sql, params);
          }
        } else {
          $F.err('Ctls2 - Tab', '不合法的动态TAB配置', cells);
        }
      } else {
        // 静态TAB控件
        var tabId,
          li,
          link,
          activeTab;
        $.each(cells.items || [], function (i, sub) {
          sub.index = i;
          tabId = a.id + '_' + i;
          li = $('<li data-ref="' + tabId + '" data-index="' + i + '" />').appendTo(tabNav).data('entity', sub);
          link = $('<a href="javascript:;" data-ref="' + tabId + '" />').appendTo(li);
          if (sub.visiable === 0) {
            li.css('display','none')
          }
          if (sub.visiable === 2) {
            activeTab = i
          }
          if (sub.icon)
            link.append($F.parseIcon(sub.icon).css('margin-right', '5px'));
          link.append('<span>' + i18nResource(a, a.instance.ognl(sub.label, a.runtime)) + '</span>');
          if (localPro(a, 'preload'))
            drawContent(sub, tabId);
        })
        a.input = a.box.wisTab({
          activeTabId: activeTab,
          autoHeight: autoHeight,
          border: border,
          contentPadding: padding,
          vertical: localPro(a, 'direct'),
          onUnselected: function (id, container, tab, callback) {
            var sub = tab.data('entity');
            if (sub.cmd) {
              var R = {
                SUB: sub,
                ID: tab.attr('data-ref'),
                DATA: parseInt(tab.attr('data-index')),
                ACTIVE: false,
                process: callback
              };
              doStructCmd(a, sub.cmd, R);
              if (!R.STOP)
                callback();
            } else
              callback();
          },
          onSelected: function (id, container, tab) {
            var sub = tab.data('entity');
            if (!container.data('draw'))
              drawContent(sub, id, container);
            const event = {
              SUB: sub,
              DATA: parseInt(tab.attr('data-index')),
              ID: tab.attr('data-ref'),
              ACTIVE: true
            }
            if (a.changeCallback) a.changeCallback('change', event)
            if (sub.cmd) {
              doStructCmd(a, sub.cmd, event);
            }
          }
        });
      }

      makeValueAndState(a);
    }

    bind(e, callback) {
      if (e !== 'change')
        this.input.bind(e, callback);
      else
        this.changeCallback = callback;
    }

    active(e) {
      if (e < 1)
        $F.err('Ctls - Tab', '不合法的标签页号：' + e);
      this.input.active(e - 1);
    }

    hideTab(array) {
      array.forEach((page)=>{
        if (page < 1) {
          $F.err('Ctls - Tab', '不合法的标签页号：' + page);
        }
        this.input.tabs[this.id + '_' + (page - 1)].css('display','none')
      })
    }

    // 针对静态tab，提供刷新tab标签的方法（标签里可能显示的是某个变量的值，所以变量改变后需要刷新）
    refreshTab() {
      var a = this;
      var cells = localPro(a, 'cells');
      if (cells.type == 1) {
        // 动态TAB控件可以绑定变量，不需要主动刷新
      } else {
        $.each(cells.items || [], function (i, sub) {
          var tabId = a.id + '_' + i;
          $('li[data-ref=' + tabId + '] span', a.box).html(i18nResource(a, a.instance.ognl(sub.label, a.runtime)))
        })
      }
    }
  }

  /*
	 * 动态页面组件：Steps
	 */
  class CtlSteps extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      // console.log(a);
      a.steps = localPro(a, 'steps', []);
      a.container = localPro(a, 'container');

      var skin = makeStyles(a);
      a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (parseInt(a.dom.css('flex-grow'))) {
        // 用于处理弹性布局单元
        // 当控件处于弹性单元时，应该将对应的宽或者高的值去除
        if (a.dom.parent().css('flex-direction') == 'column')
          a.dom.css('height', '');
        else
          a.dom.css('width', '');
      }
      a.input = $('<div class="ctl_steps" style="height:100%;width:100%" />').appendTo(a.box);
      a.instance.registerEvent('STEP_FIRST', function () {
        a.firstStep();
      }, a.runtime);
      a.instance.registerEvent('STEP_PRE', function () {
        a.preStep();
      }, a.runtime);
      a.instance.registerEvent('STEP_NEXT', function () {
        a.nextStep();
      }, a.runtime);
      a.instance.registerEvent('STEP_LAST', function () {
        a.lastStep();
      }, a.runtime);
      if (a.steps.length) {
        a.steps[0].active = true;
        a.drawSteps();
      }
      makeValueAndState(a);
    }

    injectSteps(steps) {
      const a = this;
      a.steps = steps;
      a.steps[0].active = true;
      a.drawSteps();
    }

    firstStep() {
      var a = this;
      for (var i = 0; i < a.steps.length; i++) {
        delete a.steps[i].active;
        delete a.steps[i].pass;
        if (i == 0) {
          a.steps[i].active = true;
        }
      }
    }

    preStep() {
      var a = this;
      $.each(a.steps, function (i, step) {
        if (step.active) {
          a.steps[i - 1].active = true;
          delete a.steps[i - 1].pass;
          delete step.active;
          a.drawSteps();
          return false;
        }
      })
    }

    nextStep() {
      var a = this;
      $.each(a.steps, function (i, step) {
        if (step.active && (i + 1) < a.steps.length) {
          a.steps[i + 1].active = true;
          delete step.active;
          step.pass = true;
          a.drawSteps();
          return false;
        }
      })
    }

    lastStep() {
      var a = this;
      for (var i = 0; i < a.steps.length; i++) {
        delete a.steps[i].active;
        a.steps[i].pass = true;
        if (i == (a.steps.length - 1)) {
          a.steps[i].active = true;
          delete a.steps[i].pass;
        }
      }
    }

    jumpStep(toStep) {
      var a = this;
      $.each(a.steps, function (i, step) {
        if (toStep) {
          if (step.id == toStep) {
            $.each(a.steps, function (j, _step) {
              delete _step.active;
              delete _step.pass;
              if (j < i) {
                _step.pass = true;
              }
            })
            step.active = true;
            return;
          }
        } else {
          // reset to first page
          if (i == 0)
            step.active = true;
          else
            delete step.active;
          delete step.pass;
        }
      })
      a.drawSteps();
    }

    reset() {
      this.jumpStep();
    }

    drawSteps() {
      var a = this, iconWidth;
      //Tipped.hideAll();
      Tipped.remove($('.step_item', a.input));
      a.input.empty();
      if ($E.theme == 'gfSkin' || $E.theme == 'greenSkin' || $E.theme == 'blueSkin') {
        $.each(a.steps, function (i, step) {
          var div = $('<div class="step_div"></div>').data('entity', step).appendTo(a.input);
          var item = $('<span class="step_item" />').appendTo(div);
          if (i > 0) {
            var con = $('<span class="step_con" />');
            div.before(con);
            if (step.active || step.pass) {
              con.addClass('over');
            }
            item.data('con', con);
          }
          if (step.label) {
            var label = $('<div class="step_label">' + step.label + '</div>').appendTo(div)
          }
          if (step.tooltip)
            item.attr('title', step.tooltip);
          if (step.icon)
            item.append($('<i class="fa-fw ' + $F.fas(step.icon) + '" />'));
          else {
            item.text(i + 1).addClass('number');
          }
          iconWidth = item.outerWidth();
          if (step.active || step.pass) {
            item.addClass('over');
            if (step.pass && getProp(a,'styles','state','0') === '0') {
              // click to jump back
              item.on('click', function () {
                a.jumpStep(step);
              })
            }
          }
          if (step.active) {
            console.log('====step===', step)
            if (step.cmd) {
              doStructCmd(a, step.cmd, {
                STEPS: a.steps,
                CUR: step
              })
            }
            $F.process(function () {
              a.drawPage(step.id)
            })
          }
        })
      } else {
        $.each(a.steps, function (i, step) {
          var item = $('<span class="step_item" />').data('entity', step).appendTo(a.input);
          if (i > 0) {
            var con = $('<span class="step_con" />');
            item.before(con);
            if (step.active || step.pass) {
              con.addClass('over');
            }
            item.data('con', con);
          }
          if (step.tooltip)
            item.attr('title', step.tooltip);
          if (step.icon)
            item.append($('<i class="fa-fw ' + $F.fas(step.icon) + '" />'));
          else {
            if (step.label) {
              item.text(step.label).addClass('label');
            } else
              item.text(i + 1).addClass('number');
          }
          iconWidth = item.outerWidth();
          if (step.active || step.pass) {
            item.addClass('over');
            // if (step.pass) {
            if (step.pass && getProp(a,'styles','state','0') === '0') {
              // click to jump back
              item.on('click', function () {
                a.jumpStep(step);
              })
            }
          }
          if (step.active) {
            if (step.cmd) {
              doStructCmd(a, step.cmd, {
                STEPS: a.steps,
                CUR: step
              })
            }
            $F.process(function () {
              a.drawPage(step.id)
            })
          }
        })
      }
      Tipped.create($('.step_item', a.input), {
        position: 'bottom'
      });
    }

    drawPage(pageId) {
      var a = this, container = a.runtime.ctls[a.container];
      if (!container) {
        $F.err('Ctl - Panel', '不存在的Step容器: ' + a.container)
        return;
      }

      container = container.dom.empty();

      // 嵌套子页面
      var page = $D.getPage(a.runtime, pageId);
      if (!page) {
        $F.err('Ctl - Panel', '不存在的子页面: ' + pageId)
      } else {
        if ($E.debug)
          console.log('嵌套子页面: ', page);

        a.instance.beforeDraw(a.runtime, page);
        $D.drawCtls(page.ctls, container, a.runtime, a.namespace + '.' + pageId, a.xData, a.xRow);
        a.ctls = container.data('controls');
        a.instance.afterDraw(a.runtime, page);
      }
    }
  }

  /*
	 * 动态页面组件：xitem
	 */
  class CtlXItem extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      const a = this;
      // console.log(a);
      a.changeCalls = []; // 选择改变时的回调方法队列
      a.lineHeight = localPro(a, 'lineHeight');
      a.lineSep = localPro(a, 'sep');
      a.highlight = localPro(a, 'highlight');
      a.hideEmpty = localPro(a, 'hideEmpty');
      a.wrap = localPro(a, 'wrap');
      a.step = localPro(a, 'step');
      a.thread = localPro(a, 'thread');
      a.xcmd = localPro(a, 'xcmd');
      a.ucmd = localPro(a, 'ucmd');
      a.lcmd = localPro(a, 'lcmd');
      a.sortcmd = localPro(a, 'sortcmd');

      let skin = makeStyles(a);
      a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (parseInt(a.dom.css('flex-grow'))) {
        // 用于处理弹性布局单元
        // 当控件处于弹性单元时，应该将对应的宽或者高的值去除
        if (a.dom.parent().css('flex-direction') == 'column')
          a.dom.css('height', '');
        else
          a.dom.css('width', '');
        //if (H5Utils.isChromeLegacy)
        //	a.box = $('<div class="wrapper" />').appendTo(a.dom);
      }
      a.ul = $('<ul class="xitem" style="all: inherit; list-style: none;" />').appendTo(a.box);

      if (data) a.shadowData = $F.clone(data);
      makeValueAndState(a);
    }

    val(t) {
      const a = this;
      if (t == undefined) {
        return a.data || [];
      } else {
        a.data = t;
        a.shadowData = $.extend(true, [], t);
        delete a.selected;
        // console.group('开始XITEM绘制：' + a.id)
        a.ul.empty();
        if ($.type(a.data) !== 'array') {
          $F.err('Ctls - xitem', '不合法的数组对象: ', t);
          return this;
        }

        if (a.drawThread) {
          clearTimeout(a.drawThread);
        }
        if (a.profile.ctls && a.profile.ctls.length > 0) {
          let stepCount = 0;
          if (a.thread) {
            let lc = function (i) {
              a.drawThread = setTimeout(function () {
                a.appendRow(i, a.data[i]);
                i++;
                stepCount++;
                if (i < a.data.length) {
                  if (a.step && stepCount == a.step) {
                    // 采用分页绘制
                    a.stepStatus = $('<li class="step"></li>').appendTo(a.ul);
                    $('<a href="javascript:;" style="padding:15px;display:block;text-align:right"><i class="far fa-angle-double-right fa-fw pull-left"></i><span> 加载更多...</span></a>')
                      .appendTo(a.stepStatus)
                      .on('click', function (e) {
                        e.preventDefault();
                        a.stepStatus.remove();
                        stepCount = 0;
                        lc(i);
                      })
                  } else
                    lc(i);
                } else {
                  //a.makeLineNo();
                }
              }, 1)
            };
            if (a.data.length > 0) {
              lc(0);
            }
          } else {
            let busyId = $F.busyShow();
            $.each(a.data, function (i, v) {
              a.appendRow(i, v);
            })
            $F.busyHide(busyId);
            // Make row draggable
            if (a.sortcmd) {
              a.ul.disableSelection();
              a.ul.sortable({
                containment: "parent",
                update(event, ui) {
                  let data = [];
                  $('.xitem_line', a.ul).each(function (i, v) {
                    v = $(v);
                    data.push(v.data('entity'))
                  })
                  doStructCmd(a, a.sortcmd, {
                    DATA: data
                  })
                },
                sort(event, ui) {
                  ui.item.css('background', '#efefef')
                  if (a.wrap)
                    ui.placeholder.css({
                      'display': 'inline-block',
                      'width': ui.item.width(),
                      'height': ui.item.height(),
                      'background': '#f1f0bd'
                    })
                }
              })
            }
            //a.makeLineNo();
          }
        }
        console.groupEnd();
        return this;
      }
    }

    evt(e) {
      const a = this;
      if (e == undefined) {
        return {
          type: 0,
          data: a.val()
        }
      } else {
        if (e.type) {
          let ks, r;
          for (let key in e.data) {
            ks = key.split(',');
            if (ks[0] == '-1') {
              a.val(a.data);
              return a;
            } else {
              r = parseInt(ks[0]);
              a.drawRow(r, a.ul.find('>li:eq(' + r + ')'), a.data[r]);
            }
          }
          a.shadowData = $F.clone(a.data)
          return a;
        } else {
          a.val(e.data);
          return a;
        }
      }
    }

    appendRow(row, data) {
      const a = this;

      let style = '';
      if (a.lineHeight) {
        // 行距
        style = 'padding-bottom:' + (a.lineHeight / 2) + 'px';
        //if (row > 0)
        style += ';padding-top:' + (a.lineHeight / 2) + 'px';
        if (a.wrap) {
          style += ';padding-right:' + (a.lineHeight / 2) + 'px';
          //if (row > 0)
          style += ';padding-left:' + (a.lineHeight / 2) + 'px';
        }
      }

      if (a.lineSep && row > 0) {
        // 分割线
        style += (';border-top:' + a.lineSep);
      }
      if (style !== '')
        style = ' style="' + style + '"';

      let li = $('<li class="xitem_line dyn_relative"' + style + '/>').data('entity', data);
      a.drawRow(row, li, data);
      return li.appendTo(a.ul);
    }

    drawRow(row, li, data) {
      const a = this;

      // console.log(data)
      li.empty();
      if (data == undefined || data.flag == -99)
        li.hide();

      let column = localPro(a, 'column', 0);
      if (a.wrap) {
        li.css('display', 'inline-block');
        if (column > 1) {
          //					a.ul.css({
          //						lineHeight: 0
          //					})
          li.css('width', 100 / column + '%')
        }
      }
      let lineBody = li;
      if (localPro(a, 'lineNo')) {
        // 独立行号
        let div = $('<div style="display:block" />').appendTo(li);
        $('<span class="lineNo">' + (row + 1) + '</span>').appendTo(div);
        lineBody = $('<div class="lineBody" />').appendTo(div);
      }
      lineBody.data('_dynControl', a)
      if (data !== undefined || !a.hideEmpty) {
        // 隐藏空行
        $D.drawCtls(a.profile.ctls, lineBody, a.runtime, a.namespace, data || [], row);
        a.instance.sqlexec(a.runtime);
      }

      let siblings = {}, binders = {};
      let generateBinders = function (ctls) {
        if (!ctls)
          return;
        $.each(ctls, function (k, ctl) {
          siblings[ctl.profile.id] = ctl;

          if (ctl.xitemListener) {
            // 控件可以通过binder属性绑定XITEM中的其他列值
            // console.log(ctl.xitemListener)
            for (let col in ctl.xitemListener) {
              if (!binders['c' + col])
                binders['c' + col] = [];
              binders['c' + col].push(ctl.xitemListener[col]);
            }
          }
          ctl.siblings = siblings;
          ctl.xitemBinders = binders;

          if (ctl.serial) {
            // 控件编辑后反写数据对象
            if (ctl.type !== 'TABLE' && ctl.type !== 'TABLE2') {
              ctl.bind('change', function () {
                data[ctl.serial - 1] = ctl.val();
                //console.log('XItem\'s cell data has changed-1: ' + data[ctl.serial - 1])
                if (data['flag'] !== 2)
                  data['flag'] = 1; // 0: normal, 1: updated, 2:created
                if (binders['c' + ctl.serial]) {
                  $.each(binders['c' + ctl.serial], function (i, callback) {
                    callback(data[ctl.serial - 1])
                  })
                }
                a.instance.sqlexec(a.runtime);

                if (a.dataProp) {
                  //console.log('[' + a.dataProp + '] 第' + ctl.serial + '列被修改:', a.data);
                  let cd = {};
                  cd[ctl.xRow + ',' + (ctl.serial - 1)] = ctl.val();
                  a.instance.fireValue(a.dataProp, a.runtime, {
                    type: 1,
                    data: cd
                  }, a);
                }
                if (a.ucmd) {
                  //console.log('XItem\'s cell data has changed-2: ' + data[ctl.serial - 1])
                  if (a.shadowData[ctl.xRow][ctl.serial - 1] == data[ctl.serial - 1])
                    return;
                  let oldValue = a.shadowData[ctl.xRow][ctl.serial - 1]
                  doStructCmd(a, a.ucmd, {
                    LINE: li,
                    X: row,
                    GRIDROW: row + 1,
                    ROWDATA: data,
                    GRIDCOL: ctl.serial,
                    OLDVALUE: oldValue
                  });
                  a.shadowData[ctl.xRow][ctl.serial - 1] = $F.clone(data[ctl.serial - 1])
                }
              })
            } else {
              if (a.ucmd) {
                ctl.bind('change', function () {
                  if (a.shadowData[ctl.xRow][ctl.serial - 1] == data[ctl.serial - 1])
                    return;
                  let oldValue = a.shadowData[ctl.xRow][ctl.serial - 1]
                  data[ctl.serial - 1] = ctl.val();
                  doStructCmd(a, a.ucmd, {
                    LINE: li,
                    X: row,
                    GRIDROW: row + 1,
                    ROWDATA: data,
                    GRIDCOL: ctl.serial,
                    OLDVALUE: oldValue
                  });
                  a.shadowData[ctl.xRow][ctl.serial - 1] = $F.clone(data[ctl.serial - 1]);
                })
              }
            }
          }
          generateBinders(ctl.ctls);
        })
      }
      generateBinders(lineBody.data('controls'));
      li.data('ctls', siblings);

      if (a.xcmd) {
        doStructCmd(a, a.xcmd, {
          LINE: li,
          X: row,
          GRIDROW: row + 1,
          ROWDATA: data
        });
      }
      li.on('click', function (e) {
        if (a.selected === row)
          return;

        let classes = 'selected'
        if (a.highlight)
          classes += ' ' + a.highlight;
        $('>li', a.ul).removeClass(classes);
        li.forceClass(classes);
        a.selected = row;
        if (a.lcmd) {
          // XITEM行选择命令已定义
          setTimeout(function () {
            doStructCmd(a, a.lcmd, {
              GRIDROW: row + 1,
              ROWDATA: data
            });
          }, 0)
        }
      })
    }

    makeLineNo() {
      let a = this, vi = 1;
      $('.xitem_line', a.dom).each(function (i, line) {
        line = $(line);
        if (line.is(':visible')) {
          $('.lineNo', line).text(vi);
          vi++;
        } else {
          $('.lineNo', line).text('');
        }
      })
    }

    append(data) {
      const a = this;
      a.data = a.data || [];
      data = data || [];
      data.flag = 2; // 0: normal, 1: updated, 2:created
      a.data.push(data);
      a.appendRow(a.data.length, data);
      // a.makeLineNo();

      if (a.dataProp)
        a.instance.fireValue(a.dataProp, a.runtime, a.evt(), a);
      return a.data.length;
    }

    insert(row) {
      const a = this;
      a.data = a.data || [];
      let data = [];
      data.flag = 2; // -1: removed, 0: normal, 1: updated, 2:created, 3: remove after created
      a.data.splice(row, 0, data);
      if (a.dataProp)
        a.instance.fireValue(a.dataProp, a.runtime, a.evt());
    }

    remove(row) {
      const a = this;
      row = parseInt(row);
      if (isNaN(row) || row < 1)
        return;

      // fixed by kevin, redraw xitem after remove
      // 07/02/2018
      let li = $('>li:eq(' + (row - 1) + ')', a.ul);
      li.hide();
      a.makeLineNo();

      let data = a.data[row - 1];
      a.data.splice(row - 1, 1);
      if (data !== undefined && data.flag !== 2) {
        if (!a.data.removed)
          a.data.removed = [];
        a.data.removed.push(data);
      }
      if (a.dataProp)
        a.instance.fireValue(a.dataProp, a.runtime, a.evt());
    }

    update(data, compareCol) {
      let a = this,
        entity;
      $.each(data, function (i, v) {
        $('>li', a.ul).each(function (row, li) {
          li = $(li);
          entity = li.data('entity');
          if (entity && entity[compareCol] == v[compareCol]) {
            li.data('entity', v);
            $.each(li.data('ctls'), function (k, ctl) {
              if (ctl.serial) {
                //console.log(ctl.profile.rl, ctl)
                ctl.val(v[ctl.serial - 1]);
              }
            })
            if (a.xcmd) {
              doStructCmd(a, a.xcmd, {
                CTRL: a,
                LINE: li,
                X: row,
                GRIDROW: row + 1,
                ROWDATA: v
              })
            }
            return false;
          }
        })
      })
      a.shadowData = $.extend(true, [], a.data);
    }

    bind(e, callback) {
      if (e === 'change')
        this.changeCalls.push(callback);
      else
        this.dom.bind(e, callback);
    }
  }

  /*
	 * 动态页面组件：pager
	 */
  class CtlPager extends CtlBase {
    constructor(container, profile, path, runtime) {
      super(container, profile, path, runtime);

      const a = this;
      a.pageIndex = 0;
      a.startIndex = 0; // 开始序号
      a.mode = localPro(a, 'mode', 0);
      a.exeCmd = localPro(a, 'exeCmd');
      a.runwait = localPro(a, 'runwait', 0);
      a.autoHidden = localPro(a, 'autoHidden', 0);
      a.totalProp = localPro(a, 'totalProp', 0);
      const sizes = String(localPro(a, 'pageSize', '20,50,100,200,300')).split(',').map(x => parseInt(x));
      a.pageSize = sizes[0];

      const skin = makeStyles(a);
      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.wisPage = a.input = a.dom.wisPage({
        lang: $E.lang || 'cn',
        sizes: sizes,
        callback: function (e) {
          if (e.type === 'size') {
            a.pageIndex = 0;
            a.pageSize = e.value;
          } else {
            a.pageIndex = e.value;
          }
          a.execute();
        }
      });

      if (a.mode === 1 || !a.runwait)
        a.execute();

      if (a.totalProp) {
        a.instance.bindValue(a.totalProp, a, function (e) {
          a.wisPage.nav({
            totalCount: a.instance.getValue(a.totalProp, a.runtime) || 0,
            pageIndex: a.pageIndex,
            pageCount: a.pageSize ? Math.ceil(a.instance.getValue(a.totalProp, a.runtime) / a.pageSize || 0): 0
          });
        }, true);
      }

      makeValueAndState(a);
    }

    execute() {
      var a = this;
      if (a.mode === 0) {
        // SQL分页
        if (a.exeCmd) {
          $F.process(function () {
            doStructCmd(a, a.exeCmd, {
              PAGEINDEX: a.pageIndex + 1,
              PAGESIZE: a.pageSize,
              PAGEPROP: a.dataProp
            })
          })
        } else {
          if (a.autoHidden)
            a.dom.hide();
        }
      } else if (a.mode === 1) {
        // 数组分页
        if (a.value) {
          var cs = [];
          var start = a.pageSize * a.pageIndex;
          if (a.value.length > start) {
            cs = a.value.slice(start, Math.min(a.value.length, start + a.pageSize))
          }
          var nature = localPro(a, 'nature');
          if (nature) {
            a.instance.fireValue(nature, a.runtime, {
              type: 0,
              data: cs
            }, a)
          }
        } else {
          if (a.autoHidden)
            a.dom.hide();
        }
      } else if (a.mode === 2) {
        // 数据库分页
        if (a.exeCmd) {
          $F.process(function () {
            doStructCmd(a, a.exeCmd, {
              PAGEINFO: {
                PAGEINDEX: a.pageIndex + 1,
                PAGESIZE: a.pageSize
              },
              STARTINDEX: a.pageIndex * a.pageSize,
              ENDINDEX: (a.pageIndex + 1) * a.pageSize,
              PAGEVALUE: a.instance.getValue(a.dataProp, a.runtime),
              PAGEPROP: a.dataProp,
              TOTALPROP: a.totalProp,
              TOTALPAGE: a.instance.getValue(a.totalProp, a.runtime)
            })
          })
        } else {
          if (a.autoHidden)
            a.dom.hide();
        }
      }
    }


    val(e) {
      var a = this;
      if (!arguments.length)
        return (a.mode ? a.value : a.pageIndex);

      if (a.mode === 0) {
        // SQL分页
        a.wisPage.nav({
          totalCount: e.COUNT || 0,
          pageIndex: (e.INDEX || 1) - 1,
          pageCount: e.PAGES || 0
        });
      } else if(a.mode === 1) {
        // 数组分页
        a.value = e;
        a.pageIndex = 0;
        a.wisPage.nav({
          totalCount: e.length,
          pageIndex: 0,
          pageCount: (e.length ? (a.pageSize ? Math.ceil(e.length / a.pageSize) : 1) : 0)
        });
        a.execute();
      } else {
        //  数据库分页
        a.wisPage.nav({
          totalCount: a.instance.getValue(a.totalProp, a.runtime) || 0,
          pageIndex: a.pageIndex,
          pageCount: a.pageSize ? Math.ceil(a.instance.getValue(a.totalProp, a.runtime) / a.pageSize || 0): 0
        });
      }
    }

    reset() {
      this.pageIndex = 0;
      this.execute();
    }

    lastPage(appending) {
      var a = this, page = a.wisPage;
      if (!appending || !page.totalCount || (page.totalCount % page.pageCount))
        a.pageIndex = page.pageCount - 1;
      else
        a.pageIndex = page.pageCount;
      a.execute();
    }
  }

  /**
   * 图表控件
   */
  class CtlChart extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      let a = this, skin = makeStyles(a);
      a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '"/>').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (parseInt(a.dom.css('flex-grow'))) {
        // 用于处理弹性布局单元
        // 当控件处于弹性单元时，应该将对应的宽或者高的值去除
        if (a.dom.parent().css('flex-direction') == 'column')
          a.dom.css('height', '');
        else
          a.dom.css('width', '');
        a.dom.css('overflow', 'auto');
        a.box = $('<div class="wrapper" />').appendTo(a.dom);
      } else {
        a.box = a.dom;
      }

      var mode = localPro(a, 'mode');
      a.libGuid = mode.id;
      a.chartProps = mode.form;
      a.chartType = '';
      var eventcmd = localPro(a, 'eventcmd');
      if (eventcmd) {
        a.eventcmd = function (data) {
          // data format
          // {
          // 		TYPE: {integer} 事件类型
          //		DATA: {object} 事件值
          // }
          doStructCmd(a, eventcmd, data)
        }
      }
      makeValueAndState(a);
    }

    redraw() {
      var a = this;
      $EliteChart.getInstance(a.runtime.project).draw(a);
    }

    val(t) {
      const a = this;
      if (!arguments.length)
        return a.value;
      else {
        if (a.value !== t) {
          a.value = t;
          if ($.type(t) == 'array' && t.length == 0) {
            $EliteChart.clear(a.box);
          } else {
            $EliteChart.getInstance(a.runtime.project).draw(a);
          }
        }
      }
      return a;
    }

    bind(e, callback) {
      if (e == 'click') {
        this.clickCallback = callback;
      }
    }

    refresh() {
      var a = this;
      $EliteChart.getInstance(a.runtime.project).draw(a);
    }

    /**
     * 当tab切换到激活页面时 同是resize当前退保
     */
    active() {
      if (this.chart) {
        this.resizeChart();
      }
    }

    resizeChart() {
      var a = this;
      if (a.chartType.match(/highchart/gi)) {
        a.chart && a.chart.reflow();
      } else if (a.chartType.match(/echart/gi)) {
        a.chart && a.chart.resize();
      }
    }

    setSeriesEvents(eventObj) {
      var a = this;
      if (a.chart && a.seriesEvents) {
        var plotOptions = a.chart.userOptions.plotOptions;
        if (plotOptions && plotOptions.series) {
          plotOptions.series.events = eventObj;
        } else if (plotOptions) {
          plotOptions.series = {
            events: eventObj
          };
        } else {
          plotOptions = {
            series: {
              events: eventObj
            }
          };
        }
        a.chart.update({
          plotOptions: plotOptions
        });
        a.seriesEvents = null;
      } else {
        a.seriesEvents = eventObj;
      }
    }
  }

  /**
   * 日历控件
   */
  class CtlCalendar extends CtlBase {
    constructor(container, profile, runtime, path) {
      super(container, profile, runtime, path);

      const a = this;
      const skin = makeStyles(a);
      a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '"/>').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (parseInt(a.dom.css('flex-grow'))) {
        // 用于处理弹性布局单元
        // 当控件处于弹性单元时，应该将对应的宽或者高的值去除
        if (a.dom.parent().css('flex-direction') == 'column')
          a.dom.css('height', '');
        else
          a.dom.css('width', '');
        a.dom.css('overflow', 'auto');
        a.box = a.dom; //$('<div class="wrapper" style="overflow:hidden" />').appendTo(a.dom);
      } else {
        a.box = a.dom;
      }
      a.datePicker = $('<input type="text" style="display:none" />').appendTo(a.dom);
      a.mask = 'Y-m-d';
      a.mode = localPro(a, 'mode');
      if (a.mode) {
        a.datePicker.val(new Date().format2(a.mask));
        a.datePicker.datetimepicker({
          locale: $E.lang,
          format: a.mask,
          timepicker: false,
          lang: 'zh',
          inline: true,
          onChangeDateTime: function (dp, $input) {
            $input.trigger('change');
          }
        })
      } else {
        a.customData = localPro(a, 'customData');
        a.rangecmd = localPro(a, 'rangecmd');
        a.newcmd = localPro(a, 'newcmd');
        a.editcmd = localPro(a, 'editcmd');
        a.delcmd = localPro(a, 'delcmd');
        a.showcmd = localPro(a, 'showcmd');
        a.eventcmd = localPro(a, 'eventcmd');

        a.events = localPro(a, 'events');
        a.vacations = localPro(a, 'vacations');
        a.notifies = localPro(a, 'notifies');
        a.weeks = localPro(a, 'weeks');

        var evtcol = localPro(a, 'evtcol'),
          dataIndex;
        if (evtcol) {
          dataIndex = {
            date: evtcol.d,
            title: evtcol.t,
            desc: evtcol.s,
            owner: evtcol.o
          }
        } else {
          dataIndex = {
            date: 0,
            title: 1,
            desc: 2,
            owner: 3
          }
        }
        var options = {
          lang: ($E.lang || 'zh_CN').split('_')[0],
          formatDate: a.mask,
          weeks: a.weeks,
          customData: a.customData,
          dataIndex: dataIndex,
          onSelected: function (date, dom) {
            a.cellDom = dom;
            a.datePicker.val(date.dateFormat(a.mask)).trigger('change');
          },
          setCurDate: function (date) {
            a.datePicker.val(date.dateFormat(a.mask)).trigger('change');
          },
          onDblClick: function (date, e) {
            $.each(a.dblclickCalls, function () {
              this(e, {
                DATE: date
              });
            })
          },
          onRange: function (startDate, endDate, curDate) {
            //console.log('Range, Start:' + startDate + ', End:' + endDate);
            if (a.rangecmd) {
              doStructCmd(a, a.rangecmd, {
                START: startDate.dateFormat(a.mask),
                END: endDate.dateFormat(a.mask),
                CUR: curDate
              })
            }
          },
          onAddnew: function (data, callback) {
            if (a.newcmd) {
              //console.log('New event tigger');
              doStructCmd(a, a.newcmd, {
                DATA: data,
                callback: callback
              })
            } else
              callback(data);
          },
          onEdit: function (data, callback) {
            if (a.editcmd) {
              doStructCmd(a, a.editcmd, {
                DATA: data,
                callback: callback
              })
            } else
              callback(data);
          },
          onRemove: function (data, callback) {
            if (a.delcmd) {
              doStructCmd(a, a.delcmd, {
                DATA: data,
                callback: callback
              })
            } else
              callback(data);
          },
          onShow: function (data, callback) {
            if (a.showcmd) {
              doStructCmd(a, a.showcmd, {
                DATA: data,
                callback: callback
              })
            } else
              callback({
                editable: true,
                removable: true
              })
          },
        };
        if (a.eventcmd) {
          options.onEvent = function (event, eventDom) {
            doStructCmd(a, a.eventcmd, {
              DATA: event,
              DOM: eventDom
            })
          }
        }
        a.input = a.calendar = a.dom.wisCalendar(options);

        if (a.events) {
          // 对事件变量进行监听，变量格式要求第一列为日期字符串，第二列为事件
          a.instance.bindValue(a.events, a, function (e) {
            a.calendar.markEvents(e.data);
          }, true);
        }
        if (a.notifies) {
          // 对假日变量进行监听，变量格式要求第一列为日期字符串，第二列为假日名称
          a.instance.bindValue(a.notifies, a, function (e) {
            a.calendar.markNotifies(e.data);
          }, true);
        }
        if (a.vacations) {
          // 对假日变量进行监听，变量格式要求第一列为日期字符串，第二列为假日名称
          a.instance.bindValue(a.vacations, a, function (e) {
            a.calendar.markVacations(e.data);
          }, true);
        }
      }

      makeValueAndState(a);
    }

    val(t) {
      const a = this;
      if (!arguments.length)
        return a.datePicker.val();
      else {
        try {
          if (t == '')
            a.datePicker.val('');
          else if (t.startWith == '1900') {
            t = '';
            a.instance.fireValue(a.dataProp, a.runtime, {
              type: 0,
              data: ''
            }, a);
            a.datePicker.val('');
          } else {
            //var date = Date.parseDate(t, Date.patterns.ISO8601LongPattern);
            let date = $F.parseDate(t);
            if (!date)
              date = new Date();
            a.datePicker.val(date.dateFormat(a.mask));
          }
          if (a.mode) {
            a.datePicker.datetimepicker('destroy');
            a.datePicker.datetimepicker({
              format: a.mask,
              timepicker: false,
              lang: 'zh',
              inline: true,
              onChangeDateTime: function (dp, $input) {
                $input.trigger('change');
              }
            })
          } else {
            a.calendar.current(a.datePicker.val());
          }
        } catch (e) {
          $F.err('Ctls - Calendar', e);
        }
        return this;
      }
    }

    bind(e, callback) {
      if (e == 'change')
        this.datePicker.bind(e, callback);
      else
        this.dom.bind(e, callback);
    }

    reset() {
      this.calendar && this.calendar.current(new Date());
    }

    execute() {
      this.calendar && this.calendar.current(this.datePicker.val());
    }

    driftLayer(layer) {
      if (!this.cellDom)
        return $F.err('Ctls - table', '当前没有选中的日期');
      var owner = this.cellDom;
      layer.drift(owner.offset().top, owner.offset().left, owner.offset().top + owner.height(), owner.offset().left + owner.width());
    }

    destroy() {
      this.calendar && this.calendar.destroy();
      super.destroy();
    }
  }

  /*
	 * 动态页面组件：vote
	 */
  class CtlVote extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      let a = this,
        skin = makeStyles(a),
        labelStr = localPro(a, 'label', '', true),
        lbStyle = localPro(a, 'lbStyle'),
        stars = localPro(a, 'stars', 5),
        justify = localPro(a, 'justify'),
        icon = localPro(a, 'icon', 0);

      var height = skin.styles['height'];
      if (!height || height == 'auto') {
        height = '2.2em';
      }
      skin.labelStyles['lineHeight'] = skin.inputStyles['lineHeight'] = height;
      if (skin.styles['color']) {
        skin.inputStyles['color'] = skin.styles['color'];
        delete skin.styles['color'];
      }
      if (skin.styles['backgroundColor']) {
        skin.inputStyles['backgroundColor'] = skin.styles['backgroundColor'];
        delete skin.styles['backgroundColor'];
      }

      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      if (labelStr) {
        var fakeBox = $('<div class="wl" />').appendTo(a.dom);
        a.label = $('<span class="dyn_as dyn_label" />').html(labelStr).appendTo(fakeBox);
        if (lbStyle) {
          if (lbStyle.a) {
            if (lbStyle.a == 'justify') {
              skin.labelStyles['textAlignLast'] = lbStyle.a;
            } else {
              skin.labelStyles['textAlign'] = lbStyle.a;
            }
          }
          if (lbStyle.w)
            skin.labelStyles['width'] = lbStyle.w;
          if (lbStyle.c)
            skin.labelStyles['color'] = lbStyle.c;
        }
        a.label.css(skin.labelStyles);
        a.voter = $('<div class="dyn_voter"/>').appendTo($('<div class="wlc" />').appendTo(fakeBox));
        a.input = $('<input type="text" style="display:none" />').appendTo(fakeBox);
      } else {
        a.voter = $('<div class="dyn_voter" />').appendTo(a.dom);
        a.input = $('<input type="text" style="display:none" />').appendTo(a.dom);
      }
      a.voter.css(skin.inputStyles);
      a.stars = $('<span class="rating"></span>').appendTo(a.voter);

      a.drawStars(stars, justify, icon);

      makeValueAndState(a);
    }

    drawStars(stars, justify, icon) {
      let a = this,
        html = '',
        v,
        space = localPro(a, 'space', 3);
      for (let i = stars; i > 0; i--) {
        switch (icon) {
          case 0:
            v = 'star';
            break;
          case 1:
            v = 'thumbs-up';
            break;
          case 2:
            v = 'heart';
            break;
          case 3:
            v = 'user';
            break;
          case 4:
            v = 'smile';
            break;
          case 5:
            v = 'hourglass';
            break;
          case 6:
            v = 'flag';
            break;
          case 7:
            v = 'comment';
            break;
          case 8:
            v = 'bell';
            break;
          case 9:
            v = 'battery-empty';
            break;
          case 10:
            v = 'circle';
            break;
          case 11:
            v = 'thermometer';
            break;
        }
        html += '<i class="star ' + $F.fas(v) + '" style="margin-right:' + space + 'px" data-ref="' + i + '"></i>';
      }
      a.stars.html(html);

      $('i.star', a.stars).on('click', function (e) {
        if (a.voter.hasClass('disabled'))
          return;

        a.triggerChange($(e.currentTarget));
      })
    }

    triggerChange(vote) {
      let a = this,
        list = $('i.star', a.stars).removeClass('selected'),
        cur = parseInt(vote.attr('data-ref')),
        stars = localPro(a, 'stars', 5),
        item;

      if (cur == 1 && parseInt(a.input.val()) == 1) {
        a.input.val(0).trigger('change');
        return;
      }

      for (let k = 0; k < stars; k++) {
        item = $(list[k]);
        if (parseInt(item.attr('data-ref')) <= cur)
          item.addClass('selected');
      }
      a.input.val(cur).trigger('change');
    }

    val(e) {
      var a = this;
      if (e === undefined)
        return a.input.val();

      e = parseInt(e);
      if (e <= 0)
        $('i.star', a.stars).removeClass('selected');
      else
        a.triggerChange($('i.star:eq(' + (localPro(a, 'stars', 5) - e) + ')', a.stars));
    }

    disable(e) {
      super.disable(e);
      this.voter.toggleClass('disabled', e);
    }
  }

  /**
   * 组件
   */
  class CtlComponent extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      // console.log(a);
      a.ctls = [];
      a.lazyLoad = localPro(a, 'lazy', 1);
      a._loaded = false;
      a.componentId = localPro(a, 'com');

      var skin = makeStyles(a);
      if (localPro(a, 'shell') == 2)
        a.dom = a.box = container;
      else
        a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '" data-com="' + a.componentId + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);

      a.head = profile.head;
      makeValueAndState(a);
    }

    state(e) {
      var a = this;
      if (e == undefined)
        return a.widgetState;
      a.widgetState = e;
      if (e == 1) {
        a.dom.hide();
        a.head && a.head.hide();
      } else {
        a.dom.show();
        a.head && a.head.show();
        if (a.lazyLoad && !a._loaded) {
          // 懒加载模式，且未绘制
          a._loaded = true;
          if (a.component)
            a.component.init();
        }
        a.disable(e == 2);
      }
    }

    bind(e, callback) {
      this.dom.bind(e, callback)
    }

    drift(top, left, bottom, right) {
      this.state(0);
      H5Utils.drift(top, left, bottom, right, this.dom);
    }

    initCmd(component) {
      var a = this,
        cmdId = localPro(a, 'initcmd');
      a.component = component;
      component.runtime.alias = getProp(a, 'extend', 'alias', {});
      var listeners = getProp(a, 'extend', 'listeners', {});
      if (listeners) {
        component.runtime.listeners = {};
        for (var id in listeners) {
          component.runtime.listeners[id] = function (parameters) {
            doStructCmd(a, listeners[id], parameters);
          }
        }
      }

      if (!a.lazyLoad || a._loaded) {
        a.component.init();
      }

      if (cmdId) {
        a.instance.doCmd(cmdId, {
          CTRL: a,
          COMPONENT: component
        }, a.runtime, a.id);
      }
    }

  }

  class CtlLayout extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      a.ctls = [];
      a.lazyLoad = localPro(a, 'lazy', 1);
      a._loaded = false;

      var skin = makeStyles(a);
      a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '" />').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      a.box = a.dom;

      if (!a.lazyLoad)
        a.load();

      makeValueAndState(a);
    }

    load() {
      var a = this;
      a._loaded = true;

      // Read personalize data of layout
      a.perzArr = [];
      $F.dataService('SystemDo2', {
        data: {
          token: a.runtime.project.token,
          dbPool: '',
          sqls: [{
            key: 'Dyn.layout.read',
            params: {
              C1: $E.staff.epidSql,
              C2: $E.staff.id,
              C3: a.path
            }
          }, {
            key: 'Dyn.layout.read',
            params: {
              C1: $E.staff.epidSql,
              C2: '_DEFT_',
              C3: a.path
            }
          }]
        },
        async: false,
        digest: a.runtime.project.digest,
        success: function (data) {
          if (data.code > 0) {
            //console.log(data);
            if (data.value[0].count < 0) {
              $F.err('Ctls - CtlLayout', 'Sql execute failed: ' + data.value[0].key);
              return;
            }
            a.perzArr = $F.makeJsonArray(data.value[0]);
            if (a.perzArr.length == 0) {
              // 当前账号无数据，从系统读入默认配置
              if (data.value[1].count < 0) {
                $F.err('Ctls - CtlLayout', 'Sql execute failed: ' + data.value[1].key);
                return;
              }
              a.perzArr = $F.makeJsonArray(data.value[1]);
            }
          } else
            $F.err('Ctls - layoutBox', data.message);
        }
      })

      // 读取容器内置的组件列表
      a.coms = localPro(a, 'coms', []);
      a.stack = localPro(a, 'stack', 0);
      a.mega = localPro(a, 'mega', 0);
      a.direct = localPro(a, 'direct', 0);
      a.perz = localPro(a, 'perz', 0);
      // console.info('获取容器组件列表[' + a.path + ']: ', a.coms);

      // 如果自定义数据中没有数据，则采用容器默认的组件属性进行UI处理
      if (!a.perzArr.length && a.stack > 0) {
        for (var key in a.coms) {
          if (a.coms.hasOwnProperty(key)) {
            var com = a.coms[key];
            com.props.extend = com.props.extend || {};
            //console.log(com.props.extend)
            if (com.props.extend.hide)
              continue; // 组件默认隐藏状态
            a.perzArr.push({
              STAFFID: $E.staff.id,
              COMKEY: com.id2,
              COMID: com.id,
              COMPNAME: com.name,
              X: 1,
              Y: 1,
              COLSPAN: 1,
              ROWSPAN: 1,
              TABINDEX: com.props.extend.index || 0
            })
          }
        }
      }
      a.perzArr.sort(function (a, b) {
        return a.TABINDEX > b.TABINDEX;
      })
      // console.log('获取容器自定义数据：' + a.path, a.perzArr);

      var visualCtl = [];
      if (a.stack == 1) {
        // 可堆叠容器
        var ul = $('<ul />').appendTo(a.box),
          li,
          pid;
        $.each(a.perzArr, function (i, perz) {
          if (perz.COMKEY == 'CONTAINER' || !a.coms[perz.COMKEY])
            return;

          pid = a.path.replace('.', '_') + '_' + perz.COMKEY;
          li = $('<li data-ref="' + pid + '" />').appendTo(ul).data('perz', perz);
          li.append('<a href="javascript:;" data-ref="' + pid + '">' + perz.COMPNAME + '</a>');

          if (localPro(a, 'preload')) {
            var div = $('<div id="' + pid + '" />').data('_dynControl', a);
            perz.show = true;
            var vc = {
              id: a.id + '.' + perz.COMKEY,
              name: 'component'
            };
            vc.props = a.coms[perz.COMKEY].props;
            vc.props.local = vc.props.local || {};
            vc.props.local.com = a.coms[perz.COMKEY].id;
            vc.props.styles = vc.props.styles || {};
            vc.props.styles['width'] = vc.props.styles['height'] = '100%';
            //vc.props.styles['top'] = '1px';

            $D.drawCtls([vc], div, a.runtime, a.namespace + '.' + a.id, a.xData, a.xRow);
            div.appendTo(a.box);
            a.ctls = a.ctls.concat(div.data('controls'));
          }
        })
        a.box.wisTab({
          vertical: a.direct,
          border: false,
          onSelected: function (id, container, tab) {
            var perz = tab.data('perz');
            if (!perz.show) {
              perz.show = true;
              var vc = {
                id: a.id + '.' + perz.COMKEY,
                name: 'component'
              };
              vc.props = a.coms[perz.COMKEY].props;
              vc.props.local = vc.props.local || {};
              vc.props.local.com = a.coms[perz.COMKEY].id;
              vc.props.styles = vc.props.styles || {};
              vc.props.styles['width'] = vc.props.styles['height'] = '100%';

              container.data('_dynControl', a);
              $D.drawCtls([vc], container, a.runtime, a.namespace + '.' + id, a.xData, a.xRow);
              a.ctls = a.ctls.concat(container.data('controls'));
            } else {
              let comp = CtlUtils.findComponentById(a.runtime, perz.COMID);
              if (comp)
                comp.onActive();
            }
          }
        });
      } else if (a.stack == 2) {
        // 陈列式容器
        var rowSep = localPro(a, 'rowSep', 0),
          colSep = localPro(a, 'colSep', 0);
        $.each(a.perzArr, function (i, perz) {
          if (perz.COMKEY == 'CONTAINER' || !a.coms[perz.COMKEY])
            return;

          var vc = {
            id: a.id + '.' + perz.COMKEY,
            name: 'component'
          }
          vc.props = a.coms[perz.COMKEY].props;
          vc.props.local = vc.props.local || {};
          vc.props.local.com = a.coms[perz.COMKEY].id;
          vc.props.styles = vc.props.styles || {};
          if (a.direct) {
            // 纵向陈列
            vc.props.styles.display = 'block';
            if (i > 0) {
              vc.props.styles.customStyles = vc.props.styles.customStyles || {};
              vc.props.styles.customStyles.styles += ';margin-top:' + rowSep + 'px';
            }
          } else {
            // 横向陈列
            vc.props.styles.display = 'inline-block';
            vc.props.styles.customStyles = vc.props.styles.customStyles || {};
            vc.props.styles.customStyles.styles += ';margin-bottom:' + rowSep + 'px';
            vc.props.styles.customStyles.styles += ';margin-right:' + colSep + 'px';
          }
          visualCtl.push(vc);
        })
        if (visualCtl.length > 0) {
          //console.log(visualCtl);
          a.box.data('_dynControl', a);
          $D.drawCtls(visualCtl, a.box, a.runtime, a.namespace + '.' + a.id, a.xData, a.xRow);
          a.ctls = a.ctls.concat(a.box.data('controls'));
        } else {
          if (a.perz || a.instance.project.groupId == 'SYSTEM') {
            var masker = $('<div class="dyn_layout"></a>').appendTo(a.box);
            $('<button title="' + $F.lang('ctls.layout.config') + '" type="button"><i class="far fa-2x fa-cog fa-spin"></i></button>')
              .appendTo(masker)
              .click(function () {
                // masker.remove();
                $E.openPlugin($F.lang('ctls.layout.plugin'), a.runtime.project, 'PerzComps', '', a);
              });
          }
        }
      } else if (a.stack == 3) {
        // 折叠式
        a.box.addClass('dyn_according');
        $.each(a.perzArr, function (i, perz) {
          if (perz.COMKEY == 'CONTAINER' || !a.coms[perz.COMKEY])
            return;

          var head = $('<h3 class="dyn_component_head"><span class="titlebar"><span class="icon"></span><span class="title"></span></span><span class="btnsbar"><i class="header_state fas fa-caret-circle-right"></i></span></h3>').appendTo(a.box),
            section = $('<div class="section" />').appendTo(a.box).data('_head', head),
            vcId = a.id + '.' + perz.COMKEY,
            vc = {
              id: vcId,
              name: 'component'
            },
            titlebar = $('.titlebar', head),
            btnsbar = $('.btnsbar', head);
          vc.props = a.coms[perz.COMKEY].props;
          vc.props.styles = vc.props.styles || {};
          vc.props.styles.display = 'block';
          vc.props.styles.state = 0;
          vc.props.local = vc.props.local || {};
          vc.props.local.com = a.coms[perz.COMKEY].id;
          vc.props.local.lazy = 1;
          vc.head = head;
          vc.container = section;
          $('.title', titlebar).text(vc.props.extend.title || '');
          $('.icon', titlebar).html(CtlUtils.parseIcon(vc.props.extend.icon));
          head.on('click', function (e) {
            e = $(e.currentTarget);
            if (e.hasClass('expand')) {
              e.data('_content').hide();
              e.removeClass('expand');
              $('.header_state', e).swapClass('fa-caret-circle-down', 'fa-caret-circle-right');
            } else {
              if (!localPro(a, 'epm')) {
                $('>h3.dyn_component_head.expand', a.box).click()
              }

              e.data('_content').show();
              e.addClass('expand');
              $('.header_state', e).swapClass('fa-caret-circle-right', 'fa-caret-circle-down');
              vc.ctl.state(0);
              vc.ctl.component.doCmd('win.max', {
                EVENT: {
                  SHOWTYPE: 1
                }
              }, true);
            }
          }).data('_content', section);

          var extra = vc.props.extend.extraMenu;
          if (extra) {
            $('<a href="javascript:;" class="extraMenu shell fas fa-server"/>').appendTo(btnsbar).on('click', function (e) {
              e.preventDefault();
              e.stopPropagation();
              $(document).click();

              var this$ = $(e.currentTarget), ul = $('<ul class="cpExtraMenu" />');
              $.each(extra, function (i, v) {
                if (v.type == 2) {
                  ul.append('<li class="sep"/>');
                } else {
                  var menu = $('<a href="javascript:;"><i class="icon fa-fw"></i><span class="label"></span></a>')
                    .appendTo($('<li />"').appendTo(ul))
                    .on('click', function (e) {
                      if (menu.hasClass('extra_radio')) {
                        // 单选项目
                        $('.extra_radio>i', ul).swapClass('fa-check-circle', 'fa-empty');
                        $('>i', menu).swapClass('fa-empty', 'fa-check-circle');
                      }
                      ul.hide();
                      if (vc.ctl.lazyLoad && !vc.ctl._loaded) {
                        // 懒加载模式，且未绘制
                        vc.ctl._loaded = true;
                        vc.ctl.component.init();
                      }
                      setTimeout(function () {
                        // trigger command
                        if (!head.hasClass('expand')) {
                          if (!localPro(a, 'epm')) {
                            $('>h3.header.expand', a.box).click()
                          }

                          head.data('_content').show();
                          head.addClass('expand');
                          $('.header_state', head).swapClass('fa-caret-circle-right', 'fa-caret-circle-down');
                          vc.ctl.state(0);
                        }
                        vc.ctl.component.doCmd('win.max', {
                          EVENT: {
                            SHOWTYPE: 1,
                            EXTRA: v.event
                          }
                        }, true);
                      }, 1)
                    });
                  $('.label', menu).text(v.label);
                  var icon = $F.fas(v.icon);
                  if (v.type == 1) {
                    menu.addClass('extra_radio');
                    if (v.prop) {
                      var value = a.instance.getValue(v.prop, vc.ctl.component.runtime);
                      if (value == v.event)
                        icon = 'far fa-check-circle';
                    }
                  }
                  $('.icon', menu).addClass(icon);
                }
              })
              ul.css({
                'fontSize': head.css('fontSize')
              })
              Helper.drift(ul, this$, function () {
                ul.remove();
              }, {
                'boxShadow': 'none'
              })
            });
          }
          visualCtl.push(vc);
        })
        if (visualCtl.length > 0) {
          a.ctls = $D.drawCtlsBatch(visualCtl, a.runtime, a.namespace + '.' + a.id, a.xData, a.xRow);
          a.box.data('controls', a.ctls);
          a.box.data('_dynControl', a);
        } else {
          if (a.perz || a.instance.project.groupId == 'SYSTEM') {
            var masker = $('<div class="dyn_layout"></a>').appendTo(a.box);
            $('<button title="' + $F.lang('ctls.layout.config') + '" type="button"><i class="far fa-2x fa-cog fa-spin"></i></button>')
              .appendTo(masker)
              .click(function () {
                // masker.remove();
                $E.openPlugin($F.lang('ctls.layout.plugin'), a.runtime.project, 'PerzComps', '', a);
              });
          }
        }
      } else {
        // 网格式容器
        let totalCols = 0, totalRows = 0;
        $.each(a.perzArr, function (i, perz) {
          if (perz.COMKEY === 'CONTAINER' || !a.coms[perz.COMKEY])
            return;
          totalRows = Math.max(perz.Y - 1 + perz.ROWSPAN, totalRows);
          totalCols = Math.max(perz.X - 1 + perz.COLSPAN, totalCols);
        })
        let flexHeight = (a.box.height() > totalRows * (a.mega ? 152 : 32));
        console.log('网格式布局：' + totalRows + ' * ' + totalCols);
        if (localPro(a, 'constrain')) {
          // 等比约束
          $.each(a.perzArr, function (i, perz) {
            // console.info(perz);
            if (perz.COMKEY == 'CONTAINER') {
              if (a.mega) {
                a.box.css('height', '100%');
              } else {
                a.box.css('height', perz.Y);
                a.box.css('minWidth', totalCols * 50 + 'px')
              }
            } else {
              if (!a.coms[perz.COMKEY])
                return;
              var vc = {
                id: a.id + '.' + perz.COMKEY,
                name: 'component'
              };
              vc.props = a.coms[perz.COMKEY].props;
              vc.props.local = vc.props.local || {};
              vc.props.local.com = a.coms[perz.COMKEY].id;
              vc.props.styles = vc.props.styles || {};
              vc.props.styles.position = 'absolute';
              vc.props.styles.left = ((perz.X - 1) * 100 / totalCols) + '%';
              vc.props.styles.width = (perz.COLSPAN * 100 / totalCols) + '%';
              if (flexHeight) {
                vc.props.styles.top = ((perz.Y - 1) * 100 / totalRows) + '%';
                vc.props.styles.height = (perz.ROWSPAN * 100 / totalRows) + '%';
              } else {
                vc.props.styles.top = (perz.Y - 1) * (a.mega ? 152 : 32);
                vc.props.styles.height = perz.ROWSPAN * (a.mega ? 150 : 30) + (perz.ROWSPAN - 1) * 2;
              }
              vc.props.styles.overflow = 'hidden';
              visualCtl.push(vc);
            }
          })
        } else {
          $.each(a.perzArr, function (i, perz) {
            // console.info(perz);
            if (perz.COMKEY == 'CONTAINER') {
              //  配置为容器高度
              //if (a.box.height() == 0) {
              a.box.css('height', perz.Y);
              //}
            } else {
              if (!a.coms[perz.COMKEY])
                return;
              var vc = {
                id: a.id + '.' + perz.COMKEY,
                name: 'component'
              };
              vc.props = a.coms[perz.COMKEY].props;
              vc.props.local = vc.props.local || {};
              vc.props.local.com = a.coms[perz.COMKEY].id;
              vc.props.styles = vc.props.styles || {};
              vc.props.styles.position = 'absolute';
              vc.props.styles.left = (perz.X - 1) * (a.mega ? 152 : 52);
              vc.props.styles.top = (perz.Y - 1) * (a.mega ? 152 : 32);
              vc.props.styles.width = perz.COLSPAN * (a.mega ? 150 : 50) + (perz.COLSPAN - 1) * 2;
              vc.props.styles.height = perz.ROWSPAN * (a.mega ? 150 : 30) + (perz.ROWSPAN - 1) * 2;
              vc.props.styles.overflow = 'hidden';
              visualCtl.push(vc);
            }
          })
        }
        if (visualCtl.length > 0) {
          // console.log(visualCtl)
          a.box.data('_dynControl', a);
          $D.drawCtls(visualCtl, a.box, a.runtime, a.namespace + '.' + a.id, a.xData, a.xRow);
          a.ctls = a.ctls.concat(a.box.data('controls'));
        } else {
          if (a.perz || a.instance.project.groupId == 'SYSTEM') {
            var masker = $('<div class="dyn_layout"></a>').appendTo(a.box);
            $('<button title="' + $F.lang('ctls.layout.config') + '" type="button"><i class="far fa-2x fa-cog fa-spin"></i></button>').appendTo(masker)
              .click(function () {
                // masker.remove();
                $E.openPlugin($F.lang('ctls.layout.plugin'), a.runtime.project, 'PerzComps', a.mega, a);
              });
          }
        }
      }

      if (a.perz || a.instance.project.groupId == 'SYSTEM') {
        var trigger = function (e) {
          // console.log('mousemove')
          // console.log(e)
          if (e.ctrlKey && (e.shiftKey || e.metaKey || e.altKey)) {
            // 按下了Ctrl键
            var masker = $('.dyn_layout_masker', a.dom);
            if (masker.length == 0) {
              masker = $('<div class="dyn_layout_masker unselectable"></div>').appendTo(a.dom);
              var of = a.dom.css('overflow');
              a.dom.css('overflow', 'hidden');
              $('<button title="' + $F.lang('ctls.layout.config') + '" type="button"><i class="far fa-2x fa-cog fa-spin"></i></button>').appendTo(masker)
                .click(function () {
                  masker.remove();
                  a.dom.css('overflow', of);
                  $E.openPlugin($F.lang('ctls.layout.plugin'), a.runtime.project, 'PerzComps', a.mega, a);
                });
              $('<button title="' + $F.lang('ctls.layout.exit') + '" type="button"><i class="far fa-2x fa-sign-out-alt"></i></button>').appendTo(masker)
                .click(function () {
                  masker.remove();
                  a.dom.css('overflow', of);
                });
            }
          }
        }
        a.box.on('mousemove', function (e) {
          trigger(e)
        });
        a.box.on('mouseover', function (e) {
          //trigger(e)
        });
      }

    }

    state(e) {
      var a = this;
      if (e == undefined)
        return a.widgetState;
      a.widgetState = e;
      if (e == 1) {
        a.dom.hide();
      } else {
        a.dom.show();
        if (a.lazyLoad && !a._loaded) {
          // 懒加载模式，且未绘制
          a.load();
        }
        a.disable(e == 2);
      }
    }

    bind(e, callback) {
      this.dom.bind(e, callback)
    }

    drift(top, left, bottom, right) {
      this.state(0);
      H5Utils.drift(top, left, bottom, right, this.dom);
    }

    reset(parameters) {
      var a = this;
      if (a.stack == 3) {
        a.ctls.forEach(function (ctl, i) {
          ctl.component.winMin({
            EVENT: parameters
          })
        })

        $('>h3.dyn_component_head', a.box).each(function () {
          var head = $(this);
          if (head.hasClass('expand'))
            head.trigger('click');
        })
        $('>div.section', a.box).hide();
      }
    }

    fireCps(cmdId, parameters) {
      var a = this;
      a.ctls.forEach(function (ctl, i) {
        ctl.component && ctl.component.doCmd(cmdId, {
          EVENT: parameters
        });
      })
    }
  }

  /*
	 * 动态页面组件：Panel
	 */
  class CtlPanel extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum, noDraw) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this, perzable = localPro(a, 'perzable');
      if (perzable)
        a.perfersLoad();
      if (noDraw)
        return a;
      a.build();
      if (perzable)
        a.makePerzable();
    }

    perfersLoad() {
      var a = this;
      // Perfers.entry
      // 可编辑容器
      $F.dataService('SystemDo2', {
        data: $F.sqlbean('Optional2.get', a.project.token, [a.runtime.namespace]),
        digest: a.project.digest,
        async: false,
        success: function (data) {
          if (data.code > 0) {
            var config;
            if (data.value[0].count > 0)
              config = JSON.parse(data.value[0].fields[0][0]);

            var ctlsMap = {}, waitForBind = {};

            function filter(ctls) {
              ctls.forEach(function (ctlProfile) {
                var fieldName = CtlUtils.getAttr(ctlProfile, '_fieldName');
                if (fieldName) {
                  // 参与自定义，且不在可用列表中的控件，将unavailable设为false
                  // 系统绘制时自动忽略unavailable为true的控件
                  if (config)
                    ctlProfile.unavailable = !config.contains(fieldName);
                  else
                    ctlProfile.unavailable = (CtlUtils.getAttr(ctlProfile, '_fieldState') ? false : true)
                  ctlsMap[fieldName] = ctlProfile;
                  var xi = waitForBind[fieldName];
                  if (xi) {
                    xi.forEach(function (waitBind) {
                      waitBind.unavailable = ctlProfile.unavailable;
                    })
                  }
                } else {
                  var fieldBind = CtlUtils.getAttr(ctlProfile, '_fieldBind');
                  if (fieldBind) {
                    // 绑定其他编辑字段的控件
                    var xi = ctlsMap[fieldBind];
                    if (xi) {
                      ctlProfile.unavailable = xi.unavailable;
                    } else {
                      ctlProfile.unavailable = true;
                      waitForBind[fieldBind] = waitForBind[fieldBind] || [];
                      waitForBind[fieldBind].push(ctlProfile);
                    }
                  }
                  if (ctlProfile.ctls)
                    filter(ctlProfile.ctls);
                }
              })
            };
            filter(a.profile.ctls);
            a.perfersConfig = config;
          } else
            $F.err(data.message);
        }
      })
    }

    makePerzable() {
      var a = this;
      a.box.off('mousemove').on('mousemove', function (e) {
        if (e.ctrlKey && (e.shiftKey || e.metaKey || e.altKey)) {
          // 按下了Ctrl键
          var masker = $('.dyn_layout_masker', a.dom);
          if (masker.length == 0) {
            masker = $('<div class="dyn_layout_masker unselectable"></div>').appendTo(a.dom);
            var of = a.dom.css('overflow');
            a.dom.css('overflow', 'hidden');
            $('<button title="' + $F.lang('ctls.layout.config') + '" class="flat_blue" type="button"><i class="far fa-cog fa-spin"></i></button>')
              .appendTo(masker)
              .click(function () {
                masker.remove();
                a.dom.css('overflow', of);

                // Perfers.entry
                // 收集指定容器内的所有一级子控件
                var fields = [], config = a.perfersConfig;
                var run = function (ctls) {
                  ctls.forEach(function (ctlProfile) {
                    var fieldName = CtlUtils.getAttr(ctlProfile, '_fieldName'),
                      state = CtlUtils.getAttr(ctlProfile, '_fieldState');
                    if (fieldName) {
                      // 只有定义过_fieldName的控件才回参与自定义配置
                      if (config) {
                        // 已有自定义配置记录
                        state = (config.contains(fieldName) ? 1 : 0);
                      }
                      fields.push({
                        name: fieldName,
                        desc: CtlUtils.getAttr(ctlProfile, '_fieldDesc'),
                        state: state,
                        require: CtlUtils.getAttr(ctlProfile, '_fieldRequire')
                      })
                    } else {
                      if (ctlProfile.ctls)
                        run(ctlProfile.ctls);
                    }
                  })
                }
                run(a.profile.ctls);

                // 使用控件选择器进行显隐配置  
                H5Utils.widgetsPerfer(fields, function (data, asPublic) {
                  // 按顺序手机指定可用的控件名称(_fieldName)
                  var cs = [];
                  data.forEach(function (item) {
                    if (item.state)
                      cs.push(item.name)
                  })

                  if (asPublic) {
                    $F.dataService('SystemDo1', {
                      data: {
                        token: a.project.token,
                        dbPool: '',
                        sqls: [{
                          key: 'Optional.delete',
                          params: {
                            C1: a.runtime.struct.guid
                          }
                        }, {
                          key: 'Optional.insert',
                          params: {
                            C1: a.runtime.struct.guid,
                            C2: a.runtime.struct.id,
                            C3: JSON.stringify(cs)
                          }
                        }]
                      },
                      digest: a.project.digest,
                      success: function (data) {
                        if (data.code > 0) {
                          $F.notify('自定义编辑控件更新成功');
                          a.redraw();
                        } else
                          $F.alert('自定义编辑控件更新失败');
                      }
                    })
                  } else {
                    $F.dataService('SystemDo1', {
                      data: {
                        token: a.project.token,
                        dbPool: '',
                        sqls: [{
                          key: 'Optional2.delete',
                          params: {
                            C1: a.runtime.namespace
                          }
                        }, {
                          key: 'Optional2.insert',
                          params: {
                            C1: a.runtime.namespace,
                            C2: JSON.stringify(cs)
                          }
                        }]
                      },
                      digest: a.project.digest,
                      success: function (data) {
                        if (data.code > 0) {
                          $F.notify('自定义编辑控件更新成功');
                          a.redraw();
                        } else
                          $F.alert('自定义编辑控件更新失败');
                      }
                    })
                  }
                })
              });
            $('<button title="' + $F.lang('ctls.layout.exit') + '" class="flat_red" type="button"><i class="far fa-sign-out-alt"></i></button>').appendTo(masker)
              .click(function () {
                masker.remove();
                a.dom.css('overflow', of);
              });
          }
        }
      });
    }

    build() {
      var a = this;
      //console.log(a);
      a.ctls = [];
      a.lazyLoad = localPro(a, 'lazy', 1);
      a.affix = localPro(a, 'affix');
      a.embed = localPro(a, 'subPage');
      a._loaded = false;

      var skin = makeStyles(a),
        valign = localPro(a, 'verticalAlign', 'middle');
      if (valign)
        skin.clazzs.push('valign_' + valign);
      if (!a.dom) {
        a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '"/>').appendTo(a.container);
      } else
        a.dom.attr('class', '').attr('style', '');
      a.dom.addClass(skin.clazzs.join(' ')).css(skin.styles).data('_dynControl', a);
      // if (a.affix) {
      // 	a.dom.affix({
      // 		offset: {
      // 			top: a.dom.closest('.dyn_shell').offset().top
      // 		}
      // 	})
      // }

      if (parseInt(a.dom.css('flex-grow'))) {
        // 用于处理弹性布局
        if (a.dom.parent().css('flex-direction') == 'column')
          a.dom.css('height', '0');
        else
          a.dom.css('width', '0');
        /*
				if (H5Utils.isChromeLegacy) {
					//console.log('弹性布局特殊处理: ' + H5Utils.isChromeLegacy)
					a.dom.css({
						padding : 0
					})
					a.box = $('<div class="wrapper" />').appendTo(a.dom);
					if (a.dom.css('display') == 'flex') {
						a.box.css({
							'display' : 'flex',
							'flexFlow' : a.dom.css('flexFlow')
						})
						a.dom.css({
							'display' : 'block',
							'flexFlow' : ''
						});
					}
					a.box.css({
						'overflow' : a.dom.css('overflow')
					})
					a.dom.css({
						'overflow' : ''
					})
				}
				*/
      }

      if (!a.lazyLoad) {
        a.load();
      }

      makeValueAndState(a);
    }

    load() {
      var a = this;
      a._loaded = true;

      const loadFunc = function (box) {
        if (a.embed) {
          // 嵌套子页面
          var page = $D.getPage(a.runtime, a.embed);
          if (!page) {
            $F.err('Ctl - Panel', '不存在的子页面: ' + a.embed)
          } else {
            if ($E.debug)
              console.log('嵌套子页面: ' + page.id);
            a.instance.beforeDraw(a.runtime, page);
            $D.drawCtls(page.ctls, box, a.runtime, a.namespace + '.' + a.id, a.xData, a.xRow);
            a.ctls = box.data('controls');
            a.instance.afterDraw(a.runtime, page);
          }
        } else {
          // 普通层
          if (a.profile.ctls) {
            $D.drawCtls(a.profile.ctls, box, a.runtime, a.namespace + '.' + a.id, a.xData, a.xRow);
            a.ctls = box.data('controls');
          }
        }
      }
      a.async = localPro(a, 'async');
      if (a.async) {
        a.lazyBox = $(a.box[0].cloneNode(true)).data('_dynControl', a);
        a.box.append($('<i class="fas fa-spinner fa-pulse"></i>'));
        setTimeout(() => {
          loadFunc(a.lazyBox);
          a.box.before(a.lazyBox).remove();
          if (a.dom == a.box)
            a.dom = a.box = a.lazyBox;
          else
            a.box = a.lazyBox;
        }, 10)
      } else loadFunc(a.box);
    }

    state(e) {
      var a = this;
      if (e == undefined)
        return a.widgetState;
      a.widgetState = e;
      if (e == 1) {
        a.dom.hide();
      } else {
        a.dom.show();
        if (a.lazyLoad && !a._loaded) {
          // 懒加载模式，且未绘制
          a.load();
        }
        a.disable(e == 2);
      }
    }

    bind(e, callback) {
      this.dom.bind(e, callback)
    }

    scrollTop(e) {
      this.dom.animate({"scrollTop": e})
    }

    getContainer() {
      return this.box;
    }

    resize() {
      this.dom.trigger('resize');
    }

    drawer(direct) {
      var a = this;
      if (a.stateProp) {
        var state = a.instance.getValue(a.stateProp, a.runtime);
        if (state)
          a.instance.fireValue(a.stateProp, a.runtime, {
            type: 0,
            data: 0
          }, a);
        else
          a.instance.fireValue(a.stateProp, a.runtime, {
            type: 0,
            data: 1
          }, a);
      }

      // console.info(a.dom.is(':visible'));
      a.dom.stop();
      if (direct) {
        if (a.dom.is(':visible')) {
          var o = a.dom.width();
          a.dom.animate({
            width: 0
          }, 'fast', function () {
            a.dom.css({
              width: o + 'px'
            })
            a.dom.hide();
          })
        } else {
          a.dom.show();
          var o = a.dom.width();
          a.dom.css('width', 0);
          a.dom.animate({
            width: o + 'px'
          }, 'fast')
        }
      } else {
        if (a.dom.is(':visible')) {
          var o = a.dom.height();
          a.dom.animate({
            height: 0
          }, 'fast', function () {
            a.dom.css({
              height: o + 'px'
            })
            a.dom.hide();
          })
        } else {
          a.dom.show();
          var height = a.dom.height();
          a.dom.css('height', 0);
          a.dom.animate({
            height: height + 'px'
          }, 'fast')
        }
      }
      return a;
    }

    toggle(direct) {
      return this.drawer(direct);
    }

    verify() {
      const a = this;
      const isHidden = function (ctl) {
        if (ctl.state() == 1)
          return true;
        const parentCtl = ctl.container.data('_dynControl');
        if (parentCtl)
          return isHidden(parentCtl);
        return false;
      }
      if (isHidden(a))
        return true;

      for (let i = 0; i < a.ctls.length; i++) {
        if (!a.ctls[i].verify())
          return false;
      }
      return true;
    }

    redraw() {
      var a = this, perzable = localPro(a, 'perzable');
      a.destroy(true);

      if (perzable)
        a.perfersLoad();
      beforeRender(a);
      a.dataProp = getProp(a, 'general', 'dataProp');
      a.serial = getProp(a, 'general', 'serial');
      a.stateProp = getProp(a, 'general', 'stateProp');
      a.build();
      if (perzable)
        a.makePerzable();
    }
  }

  /*
	 * 动态页面组件：Layer
	 */
  class CtlLayer extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum, noDraw) {
      super(container, profile, runtime, path, data, rowNum);

      if (noDraw)
        return this;

      var a = this;
      //console.log(a);
      a.showType = localPro(a, 'showType', 0);
      a.label = localPro(a, 'label', '', 1);
      a.lazyLoad = localPro(a, 'lazy', 1);
      a.mask = localPro(a, 'mask', 0);
      a.icon = localPro(a, 'icon');
      a.dialogSkin = localPro(a, 'dialogSkin');
      a._loaded = false;
      a.ctls = [];

      var skin = makeStyles(a);
      if (a.showType == 1 || a.showType == 4)
        skin.styles['position'] = 'relative';
      else
        skin.styles['position'] = 'absolute';
      a.sideWidth = skin.styles['width'];
      a.sideHeight = skin.styles['height'];

      skin.clazzs.push('dyn_valign_middle');
      if (skin.styles['fontSize'] == undefined)
        skin.styles['fontSize'] = container.css('font-size') || '12px';

      if (a.showType == 2) {
        // 置顶区面板
        if ($E.device == 'mobile') {
          skin.clazzs.push('custFixed');
          var fixedBox = $('header>div.fixed');
          a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '"/>').appendTo(fixedBox).addClass(skin.clazzs.join(' ')).css(skin.styles);
          a.box = a.dom;
          a.dom.css({
            position: 'relative',
            float: 'none',
            left: 0,
            top: 0,
            right: 0,
            bottom: 0,
            width: ''
          })
          a.instance.extendFixed = a.dom;
        } else {
          a.dom = $('<div data-type="' + a.type + '" data-path="' + a.path + '"/>');
          $F.err('Ctls - Layer', '非手机模式不支持置顶')
        }
      } else {
        a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '"/>').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles);
      }
      a.box.data('_dynControl', a)

      if (!a.lazyLoad) {
        a.load();
      }

      makeValueAndState(a);

      if (a.stateProp)
        a.instance.fireValue(a.stateProp, runtime, {
          type: 0,
          data: 1
        }, a);
      a.state(1)
    }

    load() {
      const a = this;
      if (a._loaded)
        return;

      a._loaded = true;
      if (a.profile.ctls) {
        $D.drawCtls(a.profile.ctls, a.box, a.runtime, a.namespace + '.' + a.id, a.xData, a.xRow);
        a.ctls = a.box.data('controls');
      }
    }

    state(e) {
      const a = this;
      if (e == undefined)
        return a.widgetState;

      e = parseInt(e);
      a.widgetState = e;
      if (e == 1) {
        if (a.showType == 1) {
          // 对话框
          var dialog = a.dom.data('dialog')
          if (dialog) {
            $F.dialogClose(dialog);
          } else
            a.dom.hide();
        } else if (a.showType == 3) {
          // 侧边栏
          var side = localPro(a, 'side', 0);
          if (a.stateProp)
            a.instance.setValue(a.stateProp, a.runtime, {
              type: 0,
              data: 1
            }, a);
          if (a.sidebar) {
            if (side == 0 || side == 2) {
              a.sidebar.css({height: 0})
            } else {
              a.sidebar.css({width: 0})
            }
            a.sidebar.stop().hide();
            a.sidebar.parent().off('click')
          }
          a.dom.hide();
        } else if (a.showType == 4) {
          // 复用面板
          a.dom.hide();
        } else {
          if (a.dom.data(Helper.masker.MASKER))
            Helper.masker.hide(a.dom);
          else
            a.dom.hide();
        }
      } else {
        if (a.lazyLoad && !a._loaded) {
          // 懒加载模式，且未绘制
          a.load();
        }
        if (a.showType == 1) {
          // 对话框
          a.precmd = localPro(a, 'precmd');
          a.okcmd = localPro(a, 'okcmd');
          a.cancelcmd = localPro(a, 'cancelcmd');
          a.closecmd = localPro(a, 'closecmd');
          a.closebtn = localPro(a, 'closebtn', 0);
          if (a.dom.is(':hidden')) {
            a.dom.css({
              left: 0,
              top: 0,
            }).show();

            var initFunc = false,
              submitFunc = false,
              cancelFunc = false,
              closeFunc = false;
            initFunc = function () {
              if (a.precmd)
                doStructCmd(a, a.precmd, {});
              a.dom.parent().css({
                padding: 10
              })
            }
            if (a.okcmd) {
              submitFunc = function (close) {
                var R = {
                  CLOSABLE: 0,
                  close: close
                };
                doStructCmd(a, a.okcmd, R);
                //console.log(values);
                if (R.CLOSABLE)
                  close();
              }
            }
            if (a.cancelcmd) {
              cancelFunc = function (close) {
                var R = {
                  CLOSABLE: 0,
                  close: close
                };
                doStructCmd(a, a.cancelcmd, R);
                if (R.CLOSABLE > 0)
                  close();
              }
            }
            closeFunc = function () {
              if (a.closecmd)
                doStructCmd(a, a.closecmd, {});
              a.dom.hide().appendTo(a.container);
              if (a.stateProp)
                a.instance.fireValue(a.stateProp, a.runtime, {
                  type: 0,
                  data: 1
                }, a);
            }

            a.dom.data('dialog', $F.createDialog(a.dom, {
              title: a.instance.ognl(a.label, a.runtime) || 'EliteCRM',
              closable: a.closebtn,
              removable: true,
              preOpen: initFunc,
              skin: a.dialogSkin,
              icon: CtlUtils.parseIcon(a.icon),
              onOpen: function () {
                var combox;
                $('select', a.dom).each(function () {
                  combox = $(this).data('ctrl');
                  if (combox && $.isFunction(combox.reset))
                    combox.reset();
                })
                $('.dyn_ckeditorInput', a.dom).each(function () {
                  $(this).data('ckeditor').redraw();
                })
                // Fixed by kevin
                // for some reason, we don't want validation with browser for us, such like hidden input dom
                $('>form', this).attr('novalidate', true);
              },
              onSubmit: submitFunc,
              onCancel: cancelFunc,
              onClose: closeFunc
            }));
          }
        } else if (a.showType == 2) {
          // 置顶区
          a.dom.show()
        } else if (a.showType == 3) {
          // 侧边栏
          var side = localPro(a, 'side', 0);
          switch (side) {
            case 0:
              a.sidebar = a.instance.topSideBar;
              break;
            case 1:
              a.sidebar = a.instance.rightSideBar;
              break;
            case 2:
              a.sidebar = a.instance.bottomSideBar;
              break;
            case 3:
              a.sidebar = a.instance.leftSideBar;
              break;
          }
          a.dom.hide();
          const appendDom = function () {
            $('i.loading', a.sidebar).hide();
            if (a.dom.data('_sidebar_loaded'))
              // 避免重复装载，造成控件事件失效
              a.dom.show();
            else {
              a.sidebar.append(a.dom.show());
              a.dom.data('_sidebar_loaded', true)
            }
            $('select', a.dom).each(function () {
              let combox = $(this).data('ctrl');
              if (combox && $.isFunction(combox.reset))
                combox.reset();
            })
            $('.dyn_ckeditorInput', a.dom).each(function () {
              $(this).data('ckeditor').redraw();
            })

            //console.log(a.sidebar.parent())
            a.sidebar.parent().off('click');
            setTimeout(function () {
              a.sidebar.parent().one('click', function (e) {
                if (side == 0 || side == 2) {
                  a.sidebar.animate({
                    'height': 0
                  }, function () {
                    a.sidebar.hide();
                    a.dom.hide();
                    if (a.stateProp)
                      a.instance.fireValue(a.stateProp, a.runtime, {
                        type: 0,
                        data: 1
                      }, a);
                  });
                } else {
                  a.sidebar.animate({
                    'width': 0
                  }, function () {
                    a.sidebar.hide();
                    if (a.stateProp)
                      a.instance.fireValue(a.stateProp, a.runtime, {
                        type: 0,
                        data: 1
                      }, a);
                  });
                }
              });
            }, 1)
          }
          const real = (side == 0 || side == 2) ? a.sideHeight : a.sideWidth;
          a.sidebar.stop();
          if (a.sidebar.children().length > 1) {
            a.sidebar.children().hide();
          }
          if ($('i.loading', a.sidebar).length == 0)
            a.sidebar.append('<i class="loading far fa-cog fa-spin" />');
          else
            $('i.loading', a.sidebar).show();
          if (side == 0 || side == 2)
            a.sidebar.show().animate({
              'height': real
            }, appendDom);
          else
            a.sidebar.show().animate({
              'width': real
            }, appendDom);
          if (a.sidebar.data("_spry_click") == undefined) {
            a.sidebar.click(function (e) {
              e.stopPropagation(); // 在区域内的点击事件阻止冒泡到document
            });
            a.sidebar.data("_spry_click", true);
          }
        } else if (a.showType == 4) {
          // 复用面板
          a.dom.show();
          $('select', a.dom).each(function () {
            var combox = $(this).data('ctrl');
            if (combox && $.isFunction(combox.reset))
              combox.reset();
          })
          $('.dyn_ckeditorInput', a.dom).each(function () {
            $(this).data('ckeditor').redraw();
          })
        } else {
          // float layer
          a.dom.css('boxShadow', "1px 3px 10px #666");
          if (a.dom.is(':hidden')) {
            if (e > 6) {
              // 用于直接显示浮动层，通常用于drift
              a.dom.css('zIndex', 1);
              a.dom.show();
            } else {
              var maskOptions = {
                clickToHide: (e !== 4 && e !== 6), // 当state==4或6时，关闭"点击即隐藏"功能
                fixed: true,
                onOpen: function () {
                  var combox;
                  $('select', a.dom).each(function () {
                    combox = $(this).data('ctrl');
                    if (combox && $.isFunction(combox.reset))
                      combox.reset();
//										else {
//											$F.log('Ctls - Panel', '没有发现COMBOX控件实例')
//											console.log(this);
//										}
                  })
                  $('.dyn_ckeditorInput', a.dom).each(function () {
                    $(this).data('ckeditor').redraw();
                  })
                },
                onClose: function () {
                  a.dom.hide().appendTo(a.container);
                  if (a.stateProp)
                    a.instance.fireValue(a.stateProp, a.runtime, {
                      type: 0,
                      data: 1
                    }, a);
                }
              }
              if (!a.mask) {
                maskOptions.container = a.instance.runtime.container;
                maskOptions.zIndex = 10001;
              }
              Helper.masker.show(a.dom, maskOptions);
            }
          }
        }
        a.disable(e == 2);
      }
    }

    bind(e, callback) {
      this.dom.bind(e, callback)
    }

    drift(top, left, bottom, right, options) {
      var a = this;
      a.state(9);
      a.dom.css('boxShadow', 'rgb(102, 102, 102) 3px 3px 16px -5px')

      $.extend(options, {
        callback: function () {
          $('.dyn_ckeditorInput', a.dom).each(function () {
            $(this).data('ckeditor').redraw();
          })
        }
      });
      H5Utils.drift(top, left, bottom, right, a.dom, options);
    }

    getContainer() {
      return this.box;
    }

    verify() {
      const a = this;
      const isHidden = function (ctl) {
        if (ctl.state() == 1)
          return true;
        const parentCtl = ctl.container.data('_dynControl');
        if (parentCtl)
          return isHidden(parentCtl);
        return false;
      }
      if (isHidden(a))
        return true;
      for (let i = 0; i < a.ctls.length; i++) {
        if (!a.ctls[i].verify())
          return false;
      }
      return true;
    }

    destroy() {
      var me = this;
      // 对话框
      var dialog = me.dom.data('dialog')
      if (dialog)
        dialog.remove();
      else
        me.dom.remove();
      super.destroy();
    }
  }

  /*
	 * 动态页面组件：Fieldset
	 */
  class CtlFieldset extends CtlPanel {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum, true);

      var a = this;
      // console.log(a);
      a.lazyLoad = localPro(a, 'lazy', 1);
      a.embed = localPro(a, 'subPage');
      a._loaded = false;

      var skin = makeStyles(a);
      skin.clazzs.push('dyn_valign_middle');

      a.dom = $('<fieldset data-type="' + a.type + '" data-path="' + a.path + '"/>').appendTo(container);
      a.box = a.dom.data('_dynControl', a);
      a.dom.addClass(skin.clazzs.join(' ')).css(skin.styles);

      if (!skin.styles['border'])
        a.dom.css('border', 0);

      var labelStr = localPro(a, 'label'),
        lbStyle = localPro(a, 'lbStyle')
      if (labelStr)
        a.label = $('<legend />').html(labelStr).appendTo(a.dom);
      if (lbStyle && a.label) {

        if (lbStyle.a == 'justify') {
          a.label.css('textAlignLast', lbStyle.a);
        } else {
          a.label.css('textAlign', lbStyle.a || '');
        }
        a.label.css('width', lbStyle.w || '')
        a.label.css('color', lbStyle.c || '')
      }
      if (!a.lazyLoad) {
        a.load();
      }

      makeValueAndState(a);
    }
  }

  /*
	 * 动态页面组件：Splitter
	 */
  class CtlSplitter extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      let a = this;
      //console.log(a);
      a.ctls = [];
      a.direct = localPro(a, 'direct');
      let minA = localPro(a, 'minA'),
        maxA = localPro(a, 'maxA'),
        minB = localPro(a, 'minB'),
        maxB = localPro(a, 'maxB'),
        defA = localPro(a, 'defA'),
        defB = localPro(a, 'defB'),
        dockA = localPro(a, 'dockA'),
        dockB = localPro(a, 'dockB');
      a.minSize = localPro(a, 'minSize');
      a.maxSize = localPro(a, 'maxSize');
      a.fixed = localPro(a, 'fixed');
      a.fixedSize = localPro(a, 'fixedSize');
      a.dockable = localPro(a, 'dockable');
      if (!a.minSize && minA)
        a.minSize = minA;
      if (!a.minSize && minB) {
        a.minSize = minB;
        a.fixed = 1;
      }
      if (!a.fixed && !a.fixedSize && defA) {
        a.fixedSize = defA
      }
      if (!a.fixedSize && defB) {
        a.fixed = 1;
        a.fixedSize = defB;
      }
      if (!a.dockable) {
        if (dockA && dockB)
          a.dockable = 3;
        else if (dockA)
          a.dockable = 1;
        else if (dockB)
          a.dockable = 2;
      }
      a.lazyload = localPro(a, 'lazy');
      a.cookie = localPro(a, 'cookie');

      let skin = makeStyles(a);
      a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '"/>')
        .appendTo(container)
        .addClass(skin.clazzs.join(' '))
        .css(skin.styles);

      if (!a.lazyload)
        a.load();

      makeValueAndState(a);
    }

    swapping() {
      var profile = this.profile, props = profile.props.local;
      profile.ctls = profile.ctls.reverse();
      if (!props)
        return;

      if (props.fixed === 1)
        delete props.fixed;
      else
        props.fixed = 1;
      if (props.dockable === 1)
        props.dockable = 2;
      else if (props.dockable === 2)
        props.dockable = 1;
      delete props.cookie;
    }

    load() {
      var a = this;
      a._loaded = true;

      var divA = $('<div class="wrapper" style="overflow:auto" />').appendTo(a.box),
        divB = $('<div class="wrapper" style="overflow:auto" />').appendTo(a.box),
        options = {
          direct: (a.direct ? 'v' : 'h'),
          fixed: a.fixed,
          handleGrab: true,
          dockable: a.dockable
        };
      if (a.cookie)
        options.cookie = 'wissplitter.' + a.path;
      if (!isNaN(a.fixedSize))
        options.fixedSize = a.fixedSize;
      if (!isNaN(a.minSize))
        options.minSize = a.minSize;
      if (!isNaN(a.maxSize))
        options.maxSize = a.maxSize;

      a.wisSplitter = a.input = a.box.wisSplitter(options);
      $D.drawCtls([a.profile.ctls[0]], divA, a.runtime, a.namespace + '.pane1', a.xData, a.xRow);
      a.ctls = divA.data('controls');
      $D.drawCtls([a.profile.ctls[1]], divB, a.runtime, a.namespace + '.pane2', a.xData, a.xRow);
      a.ctls = a.ctls.concat(divB.data('controls'));
    }

    state(e) {
      var a = this;
      if (e == 0) {
        if (a.lazyLoad && !a._loaded)
          a.load();
      }
      super.state(e)
    }

    bind(e, callback) {
      this.dom.bind(e, callback)
    }

    verify() {
      var a = this;
      var isHidden = function (ctl) {
        if (ctl.state() == 1)
          return true;
        var parentCtl = ctl.container.data('_dynControl');
        if (parentCtl)
          return isHidden(parentCtl);
        return false;
      }
      if (isHidden(a))
        return true;
      for (var i = 0; i < a.ctls.length; i++) {
        if (!a.ctls[i].verify())
          return false;
      }
      return true;
    }

    destroy() {
      this.wisSplitter && this.wisSplitter.destroy();
      super.destroy();
    }
  }

  /*
	 * 动态页面组件：Embed
	 */
  class CtlEmbed extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      //console.log(a);
      a.ctls = [];
      a.embed = localPro(a, 'subPage');
      a._loaded = false;

      var skin = makeStyles(a);
      skin.clazzs.push('dyn_valign_middle');
      skin.styles['width'] = skin.styles['height'] = '100%';
      skin.styles['position'] = 'relative';
      skin.styles['overflow'] = 'auto';
      a.dom = a.box = $('<div data-type="' + a.type + '" data-path="' + a.path + '"/>').appendTo(container).addClass(skin.clazzs.join(' ')).css(skin.styles).data('_dynControl', a);
      if (a.embed) {
        // 嵌套子页面
        var page = $D.getPage(a.runtime, a.embed);
        if (!page) {
          $F.err('Ctl - Panel', '不存在的子页面: ' + a.embed)
        } else {
          if ($E.debug)
            console.log('嵌套子页面: ' + page.id);
          a.instance.beforeDraw(a.runtime, page);
          $D.drawCtls(page.ctls, a.box, a.runtime, a.namespace + '.' + a.id, a.xData, a.xRow);
          a.ctls = a.box.data('controls');
          a.instance.afterDraw(a.runtime, page);
        }
      } else {
        // 普通层
        if (a.profile.ctls) {
          $D.drawCtls(a.profile.ctls, a.box, a.runtime, a.namespace + '.' + a.id, a.xData, a.xRow);
          a.ctls = a.box.data('controls');
        }
      }

      makeValueAndState(a);
    }

    bind(e, callback) {
      this.dom.bind(e, callback)
    }

    resize() {
      this.dom.trigger('resize');
    }

    scrollTop(e) {
      this.dom.animate({"scrollTop": e})
    }

    verify() {
      var a = this;
      var isHidden = function (ctl) {
        if (ctl.state() == 1)
          return true;
        var parentCtl = ctl.container.data('_dynControl');
        if (parentCtl)
          return isHidden(parentCtl);
        return false;
      }
      if (isHidden(a))
        return true;

      for (var i = 0; i < a.ctls.length; i++) {
        if (!a.ctls[i].verify())
          return false;
      }
      return true;
    }
  }

  /*
	 * 动态页面组件：footmenu
	 */
  class CtlFootmenu extends CtlBase {
    constructor(container, profile, runtime, path, data, rowNum) {
      super(container, profile, runtime, path, data, rowNum);

      var a = this;
      // console.log(a);

      var footer = $('footer'),
        menuBar = $('<div class="cusmenu" />').appendTo(footer),
        items = localPro(a, 'items', []);
      $.each(items, function (i, v) {
        //console.log(v);
        var link = $('<a href="javascript:;" class="abbr"></a>');
        if (v.icon)
          link.append($F.parseIcon(v.icon));
        link.append($('<span />').text(v.name));

        if (v.state) {
          try {
            var state = a.instance.getValue(v.state, runtime);
            if (!state)
              link.appendTo(menuBar);
          } catch (e) {
            $F.err('Ctls - BSMenu', e);
          }
        } else
          link.appendTo(menuBar);

        a.activing = false;
        link.on('click', function (e) {
          e.preventDefault();
          if (a.activing)
            return;

          // draw sub menus
          link.siblings().removeClass('active');
          link.forceClass('active');
          var subDiv = $('<div class="submenu"/>').appendTo(footer);
          subDiv.empty().html('');
          if (v.children) {
            $.each(v.children, function (i, v) {
              //console.log(v);
              var sublink = $('<a href="javascript:;" class="abbr" />');
              if (v.icon)
                sublink.append($F.parseIcon(v.icon));
              sublink.append($('<span />').text(v.name));

              if (v.state) {
                try {
                  var state = a.instance.getValue(v.state, runtime);
                  if (!state)
                    sublink.appendTo(subDiv);
                } catch (e) {
                  $F.err('Ctls - BSMenu', e);
                }
              } else
                sublink.appendTo(subDiv);
              sublink.on('click', function (e) {
                e.preventDefault();
                if (v.cmd) {
                  a.activing = true;
                  $F.process(function () {
                    doStructCmd(a, v.cmd);
                    a.activing = false;
                  })
                }
              })
            })
            var this$ = $(e.currentTarget);
            subDiv.css({
              left: this$.position().left,
              width: this$.outerWidth()
            })
            Helper.slide(subDiv, {
              onClose: function () {
                subDiv.remove();
              }
            });
          } else {
            if (v.cmd) {
              a.activing = true;
              $F.process(function () {
                doStructCmd(a, v.cmd);
                a.activing = false;
              })
            }
          }
        })
      });
      a.dom = a.instance.extendBSMenu = menuBar;

      makeValueAndState(a);
    }
  }

  /*
	 * 动态页面组件：Action
	 */
  class ActionBar {
    constructor(runtime) {
      var a = this;
      a.disabled = false;
      a.id = Guid.raw();
      a.name = a.type = 'ACTIONS';

      $('.dyn_actions', runtime.container).remove();
      if (runtime.instance.getValue('var.act_hide', runtime, 0) == 1)
        return;

      var actions = $('<div class="dyn_actions" />')
          .appendTo(runtime.container)
          .draggable({
            containment: runtime.container
          }),
        prev = $('<button class="ms">' + $F.lang('nav.prev') + '</button>').appendTo(actions),
        prevFlag = true,
        next = $('<button class="ms">' + $F.lang('nav.next') + '</button>').appendTo(actions),
        nextFlag = true,
        save = $('<button class="green">' + $F.lang('nav.save') + '</button>').appendTo(actions),
        saveFlag = true;
      //cancel = $('<button class="ms">取消</button>').appendTo(actions);

      next.click(function (e) {
        $F.process(function () {
          $D.nextStep(runtime);
        })
      })
      prev.click(function (e) {
        $F.process(function () {
          $D.prevStep(runtime);
        })
      })
      save.click(function (e) {
        $F.process(function () {
          var r = runtime.instance.save();
          $F.notify(r > 0 ? $F.lang('message.savesuccess') : $F.lang('message.savefailed'), r);
        })
      })

      var switchActions = function () {
        if (prevFlag || nextFlag || saveFlag)
          actions.show();
        else
          actions.hide();
      }

      if (runtime.data.local['var'] !== undefined && runtime.data.local['var']['act_hideprev'] !== undefined) {
        runtime.instance.bindValue('var.act_hideprev', {
          id: runtime.instance.id,
          runtime: runtime
        }, function (e) {
          if (e.data == 1) {
            prev.hide();
            prevFlag = false;
          } else {
            prev.show();
            prevFlag = true;
          }
          switchActions();
        });
      }
      if (runtime.flows.length < 2 || runtime.instance.getValue('var.act_hideprev', runtime, 0) == 1) {
        prev.hide();
        prevFlag = false;
      }

      if (runtime.data.local['var'] !== undefined && runtime.data.local['var']['act_hidenext'] !== undefined) {
        runtime.instance.bindValue('var.act_hidenext', {
          id: runtime.instance.id,
          runtime: runtime
        }, function (e) {
          if (e.data == 1) {
            next.hide();
            nextFlag = false;
          } else {
            next.show();
            nextFlag = true;
          }
          switchActions();
        });
      }
      if (!$D.hasNextPage(runtime) || runtime.instance.getValue('var.act_hidenext', runtime, 0) == 1) {
        next.hide();
        nextFlag = false;
      }

      if (runtime.data.local['var'] !== undefined && runtime.data.local['var']['act_hidesave'] !== undefined) {
        runtime.instance.bindValue('var.act_hidesave', {
          id: runtime.instance.id,
          runtime: runtime
        }, function (e) {
          if (e.data == 1) {
            save.hide();
            saveFlag = false;
          } else {
            save.show();
            saveFlag = true;
          }
          switchActions();
        });
      }
      if (runtime.instance.getValue('var.act_hidesave', runtime, 0) == 1 || !runtime.instance.stateful || runtime.instance.inner) {
        save.hide();
        saveFlag = false;
      }
      switchActions();
    }

    state(e) {
      if (e == 1)
        this.actions.hide();
      else {
        this.actions.show();
        this.disable(e == 2);
      }
    }
  }

  return {
    version: '2.0.20170509',
    input: function (container, profile, runtime, path, data, rowNum) {
      return new CtlInput(container, profile, runtime, path, data, rowNum);
    },
    numbox: function (container, profile, runtime, path, data, rowNum) {
      return new CtlNumbox(container, profile, runtime, path, data, rowNum);
    },
    datetime: function (container, profile, runtime, path, data, rowNum) {
      return new CtlDatetime(container, profile, runtime, path, data, rowNum);
    },
    button: function (container, profile, runtime, path, data, rowNum) {
      return new CtlButton(container, profile, runtime, path, data, rowNum);
    },
    buttonGroup: function (container, profile, runtime, path, data, rowNum) {
      return new CtlButtonGroup(container, profile, runtime, path, data, rowNum);
    },
    label: function (container, profile, runtime, path, data, rowNum) {
      return new CtlLabel(container, profile, runtime, path, data, rowNum);
    },
    combox: function (container, profile, runtime, path, data, rowNum) {
      return new CtlCombox(container, profile, runtime, path, data, rowNum);
    },
    radio: function (container, profile, runtime, path, data, rowNum) {
      return new CtlRadio(container, profile, runtime, path, data, rowNum);
    },
    checkbox: function (container, profile, runtime, path, data, rowNum) {
      return new CtlCheckbox(container, profile, runtime, path, data, rowNum);
    },
    switcher: function (container, profile, runtime, path, data, rowNum) {
      return new CtlSwitcher(container, profile, runtime, path, data, rowNum);
    },
    line: function (container, profile, runtime, path, data, rowNum) {
      return new CtlLine(container, profile, runtime, path, data, rowNum);
    },
    script: function (container, profile, runtime, path, data, rowNum) {
      return new CtlScript(container, profile, runtime, path, data, rowNum);
    },
    timeline: function (container, profile, runtime, path, data, rowNum) {
      return new CtlTimeline(container, profile, runtime, path, data, rowNum);
    },
    questions: function (container, profile, runtime, path, data, rowNum) {
      return new CtlQuestions(container, profile, runtime, path, data, rowNum);
    },
    marquee: function (container, profile, runtime, path, data, rowNum) {
      return new CtlMarquee(container, profile, runtime, path, data, rowNum);
    },
    link: function (container, profile, runtime, path, data, rowNum) {
      return new CtlLink(container, profile, runtime, path, data, rowNum);
    },
    image: function (container, profile, runtime, path, data, rowNum) {
      return new CtlImage(container, profile, runtime, path, data, rowNum);
    },
    dropdown: function (container, profile, runtime, path, data, rowNum) {
      return new CtlDropdown(container, profile, runtime, path, data, rowNum);
    },
    dropmenu: function (container, profile, runtime, path, data, rowNum) {
      return new CtlDropmenu(container, profile, runtime, path, data, rowNum);
    },
    list: function (container, profile, runtime, path, data, rowNum) {
      return new CtlList(container, profile, runtime, path, data, rowNum);
    },
    tree: function (container, profile, runtime, path, data, rowNum) {
      return new CtlTree(container, profile, runtime, path, data, rowNum);
    },
    table: function (container, profile, runtime, path, data, rowNum) {
      return new CtlTable(container, profile, runtime, path, data, rowNum);
    },
    tab: function (container, profile, runtime, path, data, rowNum) {
      return new CtlTab(container, profile, runtime, path, data, rowNum);
    },
    xitem: function (container, profile, runtime, path, data, rowNum) {
      return new CtlXItem(container, profile, runtime, path, data, rowNum);
    },
    pager: function (container, profile, runtime, path) {
      return new CtlPager(container, profile, runtime, path);
    },
    chart: function (container, profile, runtime, path, data, rowNum) {
      return new CtlChart(container, profile, runtime, path, data, rowNum);
    },
    calendar: function (container, profile, runtime, path) {
      return new CtlCalendar(container, profile, runtime, path);
    },
    vote: function (container, profile, runtime, path) {
      return new CtlVote(container, profile, runtime, path);
    },
    component: function (container, profile, runtime, path, data, rowNum) {
      return new CtlComponent(container, profile, runtime, path, data, rowNum);
    },
    layout: function (container, profile, runtime, path, data, rowNum) {
      return new CtlLayout(container, profile, runtime, path, data, rowNum);
    },
    div: function (container, profile, runtime, path, data, rowNum) {
      return new CtlPanel(container, profile, runtime, path, data, rowNum);
    },
    fieldset: function (container, profile, runtime, path, data, rowNum) {
      return new CtlFieldset(container, profile, runtime, path, data, rowNum);
    },
    splitter: function (container, profile, runtime, path, data, rowNum) {
      return new CtlSplitter(container, profile, runtime, path, data, rowNum);
    },
    embed: function (container, profile, runtime, path, data, rowNum) {
      return new CtlEmbed(container, profile, runtime, path, data, rowNum);
    },
    layer: function (container, profile, runtime, path, data, rowNum) {
      return new CtlLayer(container, profile, runtime, path, data, rowNum);
    },
    footmenu: function (container, profile, runtime, path, data, rowNum) {
      return new CtlFootmenu(container, profile, runtime, path, data, rowNum);
    },
    audio: function (container, profile, runtime, path, data, rowNum) {
      return new CtlAudio(container, profile, runtime, path, data, rowNum);
    },
    video: function (container, profile, runtime, path, data, rowNum) {
      return new CtlVideo(container, profile, runtime, path, data, rowNum);
    },
    progress: function (container, profile, runtime, path, data, rowNum) {
      return new CtlProgress(container, profile, runtime, path, data, rowNum);
    },
    iframe: function (container, profile, runtime, path, data, rowNum) {
      return new CtlIframe(container, profile, runtime, path, data, rowNum);
    },
    slider: function (container, profile, runtime, path, data, rowNum) {
      return new CtlSlider(container, profile, runtime, path, data, rowNum);
    },
    steps: function (container, profile, runtime, path, data, rowNum) {
      return new CtlSteps(container, profile, runtime, path, data, rowNum);
    },
    tag_i: function (container, profile, runtime, path, data, rowNum) {
      return new CtlTag_i(container, profile, runtime, path, data, rowNum);
    },
    yearmonth: function (container, profile, runtime, path, data, rowNum) {
      return new CtlYearMonth(container, profile, runtime, path, data, rowNum);
    },
    action: function (runtime) {
      return new ActionBar(runtime)
    }
  };
});
